patman: Add new tags for finer-grained changelog control

By default patman generates a combined changelog for the cover letter. This
may not always be desirable.

Many patches may have the same changes. These can be coalesced with
"Series-process-log: uniq", but this is imperfect. Similar changes like
"Move foo to patch 7" will not be merged with the similar "Move foo to this
patch from patch 6".

Changes may not make sense outside of the patch they are written for. For
example, a change line of "Add check for bar" does not make sense outside
of the context in which bar might be checked for. Some changes like "New"
or "Lint" may be repeated many times throughout different change logs, but
carry no useful information in a summary.

Lastly, I like to summarize the broad strokes of the changes I have made in
the cover letter, while documenting all the details in the appropriate
patches. I think this makes it easier to get a good feel for what has
changed, without making it difficult to wade through every change in the
whole series.

This patch adds two new tags to add changelog entries which only appear in
the cover letter, or only appear in the commit. Changes documented with
"Commit-changes" will only appear in the commit, and will not appear in the
cover letter. Changes documented with "Cover-changes" will not appear in
any commit, and will only appear in the cover letter.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Sean Anderson 2020-05-04 16:28:34 -04:00 committed by Simon Glass
parent b0436b9404
commit 6949f70c6d
4 changed files with 83 additions and 32 deletions

View File

@ -247,6 +247,23 @@ Series-changes: n
to update the log there and then, knowing that the script will to update the log there and then, knowing that the script will
do the rest. do the rest.
Commit-changes: n
- This line will not appear in the cover-letter changelog
<blank line>
This tag is like Series-changes, except changes in this changelog will
only appear in the changelog of the commit this tag is in. This is
useful when you want to add notes which may not make sense in the cover
letter. For example, you can have short changes such as "New" or
"Lint".
Cover-changes: n
- This line will only appear in the cover letter
<blank line>
This tag is like Series-changes, except changes in this changelog will
only appear in the cover-letter changelog. This is useful to summarize
changes made with Commit-changes, or to add additional context to
changes.
Patch-cc: Their Name <email> Patch-cc: Their Name <email>
This copies a single patch to another email address. Note that the This copies a single patch to another email address. Note that the
Cc: used by git send-email is ignored by patman, but will be Cc: used by git send-email is ignored by patman, but will be

View File

@ -65,7 +65,7 @@ parser.add_option('--no-check', action='store_false', dest='check_patch',
default=True, default=True,
help="Don't check for patch compliance") help="Don't check for patch compliance")
parser.add_option('--no-tags', action='store_false', dest='process_tags', parser.add_option('--no-tags', action='store_false', dest='process_tags',
default=True, help="Don't process subject tags as aliaes") default=True, help="Don't process subject tags as aliases")
parser.add_option('--smtp-server', type='str', parser.add_option('--smtp-server', type='str',
help="Specify the SMTP server to 'git send-email'") help="Specify the SMTP server to 'git send-email'")
parser.add_option('--test', action='store_true', dest='test', parser.add_option('--test', action='store_true', dest='test',

View File

@ -24,11 +24,8 @@ re_allowed_after_test = re.compile('^Signed-off-by:')
# Signoffs # Signoffs
re_signoff = re.compile('^Signed-off-by: *(.*)') re_signoff = re.compile('^Signed-off-by: *(.*)')
# The start of the cover letter # Cover letter tag
re_cover = re.compile('^Cover-letter:') re_cover = re.compile('^Cover-([a-z-]*): *(.*)')
# A cover letter Cc
re_cover_cc = re.compile('^Cover-letter-cc: *(.*)')
# Patch series tag # Patch series tag
re_series_tag = re.compile('^Series-([a-z-]*): *(.*)') re_series_tag = re.compile('^Series-([a-z-]*): *(.*)')
@ -65,7 +62,7 @@ class PatchStream:
def __init__(self, series, name=None, is_log=False): def __init__(self, series, name=None, is_log=False):
self.skip_blank = False # True to skip a single blank line self.skip_blank = False # True to skip a single blank line
self.found_test = False # Found a TEST= line self.found_test = False # Found a TEST= line
self.lines_after_test = 0 # MNumber of lines found after TEST= self.lines_after_test = 0 # Number of lines found after TEST=
self.warn = [] # List of warnings we have collected self.warn = [] # List of warnings we have collected
self.linenum = 1 # Output line number we are up to self.linenum = 1 # Output line number we are up to
self.in_section = None # Name of start...END section we are in self.in_section = None # Name of start...END section we are in
@ -73,7 +70,8 @@ class PatchStream:
self.section = [] # The current section...END section self.section = [] # The current section...END section
self.series = series # Info about the patch series self.series = series # Info about the patch series
self.is_log = is_log # True if indent like git log self.is_log = is_log # True if indent like git log
self.in_change = 0 # Non-zero if we are in a change list self.in_change = None # Name of the change list we are in
self.change_version = 0 # Non-zero if we are in a change list
self.blank_count = 0 # Number of blank lines stored up self.blank_count = 0 # Number of blank lines stored up
self.state = STATE_MSG_HEADER # What state are we in? self.state = STATE_MSG_HEADER # What state are we in?
self.signoff = [] # Contents of signoff line self.signoff = [] # Contents of signoff line
@ -124,6 +122,22 @@ class PatchStream:
self.skip_blank = True self.skip_blank = True
self.section = [] self.section = []
def ParseVersion(self, value, line):
"""Parse a version from a *-changes tag
Args:
value: Tag value (part after 'xxx-changes: '
line: Source line containing tag
Returns:
The version as an integer
"""
try:
return int(value)
except ValueError as str:
raise ValueError("%s: Cannot decode version info '%s'" %
(self.commit.hash, line))
def ProcessLine(self, line): def ProcessLine(self, line):
"""Process a single line of a patch file or commit log """Process a single line of a patch file or commit log
@ -163,7 +177,6 @@ class PatchStream:
change_id_match = re_change_id.match(line) change_id_match = re_change_id.match(line)
commit_tag_match = re_commit_tag.match(line) commit_tag_match = re_commit_tag.match(line)
cover_match = re_cover.match(line) cover_match = re_cover.match(line)
cover_cc_match = re_cover_cc.match(line)
signoff_match = re_signoff.match(line) signoff_match = re_signoff.match(line)
tag_match = None tag_match = None
if self.state == STATE_PATCH_HEADER: if self.state == STATE_PATCH_HEADER:
@ -183,8 +196,7 @@ class PatchStream:
# If a tag is detected, or a new commit starts # If a tag is detected, or a new commit starts
if series_tag_match or commit_tag_match or change_id_match or \ if series_tag_match or commit_tag_match or change_id_match or \
cover_match or cover_cc_match or signoff_match or \ cover_match or signoff_match or self.state == STATE_MSG_HEADER:
self.state == STATE_MSG_HEADER:
# but we are already in a section, this means 'END' is missing # but we are already in a section, this means 'END' is missing
# for that section, fix it up. # for that section, fix it up.
if self.in_section: if self.in_section:
@ -205,8 +217,9 @@ class PatchStream:
# but we are already in a change list, that means a blank line # but we are already in a change list, that means a blank line
# is missing, fix it up. # is missing, fix it up.
if self.in_change: if self.in_change:
self.warn.append("Missing 'blank line' in section 'Series-changes'") self.warn.append("Missing 'blank line' in section '%s-changes'" % self.in_change)
self.in_change = 0 self.in_change = None
self.change_version = 0
# If we are in a section, keep collecting lines until we see END # If we are in a section, keep collecting lines until we see END
if self.in_section: if self.in_section:
@ -242,26 +255,37 @@ class PatchStream:
elif self.skip_blank and is_blank: elif self.skip_blank and is_blank:
self.skip_blank = False self.skip_blank = False
# Detect the start of a cover letter section # Detect Cover-xxx tags
elif cover_match: elif cover_match:
self.in_section = 'cover' name = cover_match.group(1)
self.skip_blank = False value = cover_match.group(2)
if name == 'letter':
elif cover_cc_match: self.in_section = 'cover'
value = cover_cc_match.group(1) self.skip_blank = False
self.AddToSeries(line, 'cover-cc', value) elif name == 'letter-cc':
self.AddToSeries(line, 'cover-cc', value)
elif name == 'changes':
self.in_change = 'Cover'
self.change_version = self.ParseVersion(value, line)
# If we are in a change list, key collected lines until a blank one # If we are in a change list, key collected lines until a blank one
elif self.in_change: elif self.in_change:
if is_blank: if is_blank:
# Blank line ends this change list # Blank line ends this change list
self.in_change = 0 self.in_change = None
self.change_version = 0
elif line == '---': elif line == '---':
self.in_change = 0 self.in_change = None
self.change_version = 0
out = self.ProcessLine(line) out = self.ProcessLine(line)
else: else:
if self.is_log: if self.is_log:
self.series.AddChange(self.in_change, self.commit, line) if self.in_change == 'Series':
self.series.AddChange(self.change_version, self.commit, line)
elif self.in_change == 'Cover':
self.series.AddChange(self.change_version, None, line)
elif self.in_change == 'Commit':
self.commit.AddChange(self.change_version, line)
self.skip_blank = False self.skip_blank = False
# Detect Series-xxx tags # Detect Series-xxx tags
@ -270,12 +294,8 @@ class PatchStream:
value = series_tag_match.group(2) value = series_tag_match.group(2)
if name == 'changes': if name == 'changes':
# value is the version number: e.g. 1, or 2 # value is the version number: e.g. 1, or 2
try: self.in_change = 'Series'
value = int(value) self.change_version = self.ParseVersion(value, line)
except ValueError as str:
raise ValueError("%s: Cannot decode version info '%s'" %
(self.commit.hash, line))
self.in_change = int(value)
else: else:
self.AddToSeries(line, name, value) self.AddToSeries(line, name, value)
self.skip_blank = True self.skip_blank = True
@ -297,6 +317,9 @@ class PatchStream:
if name == 'notes': if name == 'notes':
self.AddToCommit(line, name, value) self.AddToCommit(line, name, value)
self.skip_blank = True self.skip_blank = True
elif name == 'changes':
self.in_change = 'Commit'
self.change_version = self.ParseVersion(value, line)
# Detect the start of a new commit # Detect the start of a new commit
elif commit_match: elif commit_match:
@ -340,7 +363,7 @@ class PatchStream:
elif line == '---': elif line == '---':
self.state = STATE_DIFFS self.state = STATE_DIFFS
# Output the tags (signeoff first), then change list # Output the tags (signoff first), then change list
out = [] out = []
log = self.series.MakeChangeLog(self.commit) log = self.series.MakeChangeLog(self.commit)
out += [line] out += [line]

View File

@ -2,6 +2,9 @@
# Copyright (c) 2011 The Chromium OS Authors. # Copyright (c) 2011 The Chromium OS Authors.
# #
from __future__ import print_function
import collections
import itertools import itertools
import os import os
@ -156,7 +159,15 @@ class Series(dict):
- Fix the widget - Fix the widget
- Jog the dial - Jog the dial
""" """
versions = sorted(self.changes, reverse=True) # Collect changes from the series and this commit
changes = collections.defaultdict(list)
for version, changelist in self.changes.items():
changes[version] += changelist
if commit:
for version, changelist in commit.changes.items():
changes[version] += [[commit, text] for text in changelist]
versions = sorted(changes, reverse=True)
newest_version = 1 newest_version = 1
if 'version' in self: if 'version' in self:
newest_version = max(newest_version, int(self.version)) newest_version = max(newest_version, int(self.version))
@ -169,7 +180,7 @@ class Series(dict):
need_blank = False need_blank = False
for version in versions: for version in versions:
out = [] out = []
for this_commit, text in self.changes[version]: for this_commit, text in changes[version]:
if commit and this_commit != commit: if commit and this_commit != commit:
continue continue
if 'uniq' not in process_it or text not in out: if 'uniq' not in process_it or text not in out: