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 <sean...@gmail.com>
Reviewed-by: Simon Glass <s...@chromium.org>
---

Changes in v4:
- Document ParseVersion
- Fix typos in commit message

Changes in v2:
- Add documentation for new tags
- Switch to using commit tags for changelog control, instead of
  command-line options

 tools/patman/README         | 17 ++++++++
 tools/patman/main.py        |  2 +-
 tools/patman/patchstream.py | 81 ++++++++++++++++++++++++-------------
 tools/patman/series.py      | 15 ++++++-
 4 files changed, 83 insertions(+), 32 deletions(-)

diff --git a/tools/patman/README b/tools/patman/README
index d1d9891c4c..5a67a49e88 100644
--- a/tools/patman/README
+++ b/tools/patman/README
@@ -247,6 +247,23 @@ Series-changes: n
        to update the log there and then, knowing that the script will
        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>
        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
diff --git a/tools/patman/main.py b/tools/patman/main.py
index f3d9c0c434..2c21a3130e 100755
--- a/tools/patman/main.py
+++ b/tools/patman/main.py
@@ -62,7 +62,7 @@ parser.add_option('--no-check', action='store_false', 
dest='check_patch',
                   default=True,
                   help="Don't check for patch compliance")
 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',
                   help="Specify the SMTP server to 'git send-email'")
 parser.add_option('-T', '--thread', action='store_true', dest='thread',
diff --git a/tools/patman/patchstream.py b/tools/patman/patchstream.py
index 405297505c..871c66aeb1 100644
--- a/tools/patman/patchstream.py
+++ b/tools/patman/patchstream.py
@@ -24,11 +24,8 @@ re_allowed_after_test = re.compile('^Signed-off-by:')
 # Signoffs
 re_signoff = re.compile('^Signed-off-by: *(.*)')
 
-# The start of the cover letter
-re_cover = re.compile('^Cover-letter:')
-
-# A cover letter Cc
-re_cover_cc = re.compile('^Cover-letter-cc: *(.*)')
+# Cover letter tag
+re_cover = re.compile('^Cover-([a-z-]*): *(.*)')
 
 # Patch series tag
 re_series_tag = re.compile('^Series-([a-z-]*): *(.*)')
@@ -65,7 +62,7 @@ class PatchStream:
     def __init__(self, series, name=None, is_log=False):
         self.skip_blank = False          # True to skip a single blank 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.linenum = 1                 # Output line number we are up to
         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.series = series             # Info about the patch series
         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.state = STATE_MSG_HEADER    # What state are we in?
         self.signoff = []                # Contents of signoff line
@@ -124,6 +122,22 @@ class PatchStream:
             self.skip_blank = True
             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):
         """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)
         commit_tag_match = re_commit_tag.match(line)
         cover_match = re_cover.match(line)
-        cover_cc_match = re_cover_cc.match(line)
         signoff_match = re_signoff.match(line)
         tag_match = None
         if self.state == STATE_PATCH_HEADER:
@@ -183,8 +196,7 @@ class PatchStream:
 
         # If a tag is detected, or a new commit starts
         if series_tag_match or commit_tag_match or change_id_match or \
-           cover_match or cover_cc_match or signoff_match or \
-           self.state == STATE_MSG_HEADER:
+           cover_match or signoff_match or self.state == STATE_MSG_HEADER:
             # but we are already in a section, this means 'END' is missing
             # for that section, fix it up.
             if self.in_section:
@@ -205,8 +217,9 @@ class PatchStream:
             # but we are already in a change list, that means a blank line
             # is missing, fix it up.
             if self.in_change:
-                self.warn.append("Missing 'blank line' in section 
'Series-changes'")
-                self.in_change = 0
+                self.warn.append("Missing 'blank line' in section 
'%s-changes'" % self.in_change)
+                self.in_change = None
+                self.change_version = 0
 
         # If we are in a section, keep collecting lines until we see END
         if self.in_section:
@@ -242,26 +255,37 @@ class PatchStream:
         elif self.skip_blank and is_blank:
             self.skip_blank = False
 
-        # Detect the start of a cover letter section
+        # Detect Cover-xxx tags
         elif cover_match:
-            self.in_section = 'cover'
-            self.skip_blank = False
-
-        elif cover_cc_match:
-            value = cover_cc_match.group(1)
-            self.AddToSeries(line, 'cover-cc', value)
+            name = cover_match.group(1)
+            value = cover_match.group(2)
+            if name == 'letter':
+                self.in_section = 'cover'
+                self.skip_blank = False
+            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
         elif self.in_change:
             if is_blank:
                 # Blank line ends this change list
-                self.in_change = 0
+                self.in_change = None
+                self.change_version = 0
             elif line == '---':
-                self.in_change = 0
+                self.in_change = None
+                self.change_version = 0
                 out = self.ProcessLine(line)
             else:
                 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
 
         # Detect Series-xxx tags
@@ -270,12 +294,8 @@ class PatchStream:
             value = series_tag_match.group(2)
             if name == 'changes':
                 # value is the version number: e.g. 1, or 2
-                try:
-                    value = int(value)
-                except ValueError as str:
-                    raise ValueError("%s: Cannot decode version info '%s'" %
-                        (self.commit.hash, line))
-                self.in_change = int(value)
+                self.in_change = 'Series'
+                self.change_version = self.ParseVersion(value, line)
             else:
                 self.AddToSeries(line, name, value)
                 self.skip_blank = True
@@ -297,6 +317,9 @@ class PatchStream:
             if name == 'notes':
                 self.AddToCommit(line, name, value)
                 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
         elif commit_match:
@@ -340,7 +363,7 @@ class PatchStream:
             elif line == '---':
                 self.state = STATE_DIFFS
 
-                # Output the tags (signeoff first), then change list
+                # Output the tags (signoff first), then change list
                 out = []
                 log = self.series.MakeChangeLog(self.commit)
                 out += [line]
diff --git a/tools/patman/series.py b/tools/patman/series.py
index be6f061122..ca7ca556dc 100644
--- a/tools/patman/series.py
+++ b/tools/patman/series.py
@@ -2,6 +2,9 @@
 # Copyright (c) 2011 The Chromium OS Authors.
 #
 
+from __future__ import print_function
+
+import collections
 import itertools
 import os
 
@@ -156,7 +159,15 @@ class Series(dict):
             - Fix the widget
             - 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
         if 'version' in self:
             newest_version = max(newest_version, int(self.version))
@@ -169,7 +180,7 @@ class Series(dict):
         need_blank = False
         for version in versions:
             out = []
-            for this_commit, text in self.changes[version]:
+            for this_commit, text in changes[version]:
                 if commit and this_commit != commit:
                     continue
                 if 'uniq' not in process_it or text not in out:
-- 
2.26.2

Reply via email to