diff options
Diffstat (limited to 'tools/patman')
-rw-r--r-- | tools/patman/README | 8 | ||||
-rw-r--r-- | tools/patman/commit.py | 3 | ||||
-rw-r--r-- | tools/patman/cros_subprocess.py | 3 | ||||
-rw-r--r-- | tools/patman/patchstream.py | 64 | ||||
-rw-r--r-- | tools/patman/test.py | 15 | ||||
-rw-r--r-- | tools/patman/tools.py | 23 |
6 files changed, 100 insertions, 16 deletions
diff --git a/tools/patman/README b/tools/patman/README index 7917fc8bdc..02d5829744 100644 --- a/tools/patman/README +++ b/tools/patman/README @@ -259,12 +259,18 @@ Series-process-log: sort, uniq unique entries. If omitted, no change log processing is done. Separate each tag with a comma. +Change-Id: + This tag is stripped out but is used to generate the Message-Id + of the emails that will be sent. When you keep the Change-Id the + same you are asserting that this is a slightly different version + (but logically the same patch) as other patches that have been + sent out with the same Change-Id. + Various other tags are silently removed, like these Chrome OS and Gerrit tags: BUG=... TEST=... -Change-Id: Review URL: Reviewed-on: Commit-xxxx: (except Commit-notes) diff --git a/tools/patman/commit.py b/tools/patman/commit.py index 2bf3a0ba5b..48d0529c53 100644 --- a/tools/patman/commit.py +++ b/tools/patman/commit.py @@ -21,6 +21,8 @@ class Commit: The dict is indexed by change version (an integer) cc_list: List of people to aliases/emails to cc on this commit notes: List of lines in the commit (not series) notes + change_id: the Change-Id: tag that was stripped from this commit + and can be used to generate the Message-Id. """ def __init__(self, hash): self.hash = hash @@ -30,6 +32,7 @@ class Commit: self.cc_list = [] self.signoff_set = set() self.notes = [] + self.change_id = None def AddChange(self, version, info): """Add a new change line to the change list for a version. diff --git a/tools/patman/cros_subprocess.py b/tools/patman/cros_subprocess.py index 06be64cc2c..0f0d60dfb7 100644 --- a/tools/patman/cros_subprocess.py +++ b/tools/patman/cros_subprocess.py @@ -54,7 +54,7 @@ class Popen(subprocess.Popen): """ def __init__(self, args, stdin=None, stdout=PIPE_PTY, stderr=PIPE_PTY, - shell=False, cwd=None, env=None, binary=False, **kwargs): + shell=False, cwd=None, env=None, **kwargs): """Cut-down constructor Args: @@ -72,7 +72,6 @@ class Popen(subprocess.Popen): """ stdout_pty = None stderr_pty = None - self.binary = binary if stdout == PIPE_PTY: stdout_pty = pty.openpty() diff --git a/tools/patman/patchstream.py b/tools/patman/patchstream.py index b6455b0fa3..ef06606297 100644 --- a/tools/patman/patchstream.py +++ b/tools/patman/patchstream.py @@ -2,6 +2,7 @@ # Copyright (c) 2011 The Chromium OS Authors. # +import datetime import math import os import re @@ -14,7 +15,7 @@ import gitutil from series import Series # Tags that we detect and remove -re_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Change-Id:|^Review URL:' +re_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Review URL:' '|Reviewed-on:|Commit-\w*:') # Lines which are allowed after a TEST= line @@ -32,6 +33,9 @@ re_cover_cc = re.compile('^Cover-letter-cc: *(.*)') # Patch series tag re_series_tag = re.compile('^Series-([a-z-]*): *(.*)') +# Change-Id will be used to generate the Message-Id and then be stripped +re_change_id = re.compile('^Change-Id: *(.*)') + # Commit series tag re_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)') @@ -156,6 +160,7 @@ class PatchStream: # Handle state transition and skipping blank lines series_tag_match = re_series_tag.match(line) + 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) @@ -177,7 +182,7 @@ class PatchStream: self.state = STATE_MSG_HEADER # If a tag is detected, or a new commit starts - if series_tag_match or commit_tag_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 \ self.state == STATE_MSG_HEADER: # but we are already in a section, this means 'END' is missing @@ -275,6 +280,16 @@ class PatchStream: self.AddToSeries(line, name, value) self.skip_blank = True + # Detect Change-Id tags + elif change_id_match: + value = change_id_match.group(1) + if self.is_log: + if self.commit.change_id: + raise ValueError("%s: Two Change-Ids: '%s' vs. '%s'" % + (self.commit.hash, self.commit.change_id, value)) + self.commit.change_id = value + self.skip_blank = True + # Detect Commit-xxx tags elif commit_tag_match: name = commit_tag_match.group(1) @@ -345,6 +360,47 @@ class PatchStream: self.warn.append('Found %d lines after TEST=' % self.lines_after_test) + def WriteMessageId(self, outfd): + """Write the Message-Id into the output. + + This is based on the Change-Id in the original patch, the version, + and the prefix. + + Args: + outfd: Output stream file object + """ + if not self.commit.change_id: + return + + # If the count is -1 we're testing, so use a fixed time + if self.commit.count == -1: + time_now = datetime.datetime(1999, 12, 31, 23, 59, 59) + else: + time_now = datetime.datetime.now() + + # In theory there is email.utils.make_msgid() which would be nice + # to use, but it already produces something way too long and thus + # will produce ugly commit lines if someone throws this into + # a "Link:" tag in the final commit. So (sigh) roll our own. + + # Start with the time; presumably we wouldn't send the same series + # with the same Change-Id at the exact same second. + parts = [time_now.strftime("%Y%m%d%H%M%S")] + + # These seem like they would be nice to include. + if 'prefix' in self.series: + parts.append(self.series['prefix']) + if 'version' in self.series: + parts.append("v%s" % self.series['version']) + + parts.append(str(self.commit.count + 1)) + + # The Change-Id must be last, right before the @ + parts.append(self.commit.change_id) + + # Join parts together with "." and write it out. + outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts)) + def ProcessStream(self, infd, outfd): """Copy a stream from infd to outfd, filtering out unwanting things. @@ -358,6 +414,9 @@ class PatchStream: fname = None last_fname = None re_fname = re.compile('diff --git a/(.*) b/.*') + + self.WriteMessageId(outfd) + while True: line = infd.readline() if not line: @@ -481,6 +540,7 @@ def FixPatches(series, fnames): for fname in fnames: commit = series.commits[count] commit.patch = fname + commit.count = count result = FixPatch(backup_dir, fname, series, commit) if result: print('%d warnings for %s:' % (len(result), fname)) diff --git a/tools/patman/test.py b/tools/patman/test.py index e1b94bd1a7..cc61c20606 100644 --- a/tools/patman/test.py +++ b/tools/patman/test.py @@ -12,6 +12,7 @@ import checkpatch import gitutil import patchstream import series +import commit class TestPatch(unittest.TestCase): @@ -48,7 +49,8 @@ Signed-off-by: Simon Glass <sjg@chromium.org> arch/arm/cpu/armv7/tegra2/ap20.c | 57 ++---- arch/arm/cpu/armv7/tegra2/clock.c | 163 +++++++++++++++++ ''' - expected=''' + expected='''Message-Id: <19991231235959.0.I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413@changeid> + From 656c9a8c31fa65859d924cd21da920d6ba537fad Mon Sep 17 00:00:00 2001 From: Simon Glass <sjg@chromium.org> @@ -79,7 +81,16 @@ Signed-off-by: Simon Glass <sjg@chromium.org> expfd.write(expected) expfd.close() - patchstream.FixPatch(None, inname, series.Series(), None) + # Normally by the time we call FixPatch we've already collected + # metadata. Here, we haven't, but at least fake up something. + # Set the "count" to -1 which tells FixPatch to use a bogus/fixed + # time for generating the Message-Id. + com = commit.Commit('') + com.change_id = 'I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413' + com.count = -1 + + patchstream.FixPatch(None, inname, series.Series(), com) + rc = os.system('diff -u %s %s' % (inname, expname)) self.assertEqual(rc, 0) diff --git a/tools/patman/tools.py b/tools/patman/tools.py index 0d4705db76..4a7fcdad21 100644 --- a/tools/patman/tools.py +++ b/tools/patman/tools.py @@ -125,7 +125,7 @@ def GetInputFilename(fname): Returns: The full path of the filename, within the input directory """ - if not indir: + if not indir or fname[:1] == '/': return fname for dirname in indir: pathname = os.path.join(dirname, fname) @@ -186,7 +186,7 @@ def PathHasFile(path_spec, fname): return True return False -def Run(name, *args, **kwargs): +def Run(name, *args): """Run a tool with some arguments This runs a 'tool', which is a program used by binman to process files and @@ -196,7 +196,6 @@ def Run(name, *args, **kwargs): Args: name: Command name to run args: Arguments to the tool - kwargs: Options to pass to command.run() Returns: CommandResult object @@ -206,8 +205,14 @@ def Run(name, *args, **kwargs): if tool_search_paths: env = dict(os.environ) env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH'] - return command.Run(name, *args, capture=True, - capture_stderr=True, env=env, **kwargs) + all_args = (name,) + args + result = command.RunPipe([all_args], capture=True, capture_stderr=True, + env=env, raise_on_error=False) + if result.return_code: + raise Exception("Error %d running '%s': %s" % + (result.return_code,' '.join(all_args), + result.stderr)) + return result.stdout except: if env and not PathHasFile(env['PATH'], name): msg = "Please install tool '%s'" % name @@ -401,14 +406,14 @@ def Compress(indata, algo, with_header=True): fname = GetOutputFilename('%s.comp.tmp' % algo) WriteFile(fname, indata) if algo == 'lz4': - data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True) + data = Run('lz4', '--no-frame-crc', '-c', fname) # cbfstool uses a very old version of lzma elif algo == 'lzma': outfname = GetOutputFilename('%s.comp.otmp' % algo) Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8') data = ReadFile(outfname) elif algo == 'gzip': - data = Run('gzip', '-c', fname, binary=True) + data = Run('gzip', '-c', fname) else: raise ValueError("Unknown algorithm '%s'" % algo) if with_header: @@ -441,13 +446,13 @@ def Decompress(indata, algo, with_header=True): with open(fname, 'wb') as fd: fd.write(indata) if algo == 'lz4': - data = Run('lz4', '-dc', fname, binary=True) + data = Run('lz4', '-dc', fname) elif algo == 'lzma': outfname = GetOutputFilename('%s.decomp.otmp' % algo) Run('lzma_alone', 'd', fname, outfname) data = ReadFile(outfname) elif algo == 'gzip': - data = Run('gzip', '-cd', fname, binary=True) + data = Run('gzip', '-cd', fname) else: raise ValueError("Unknown algorithm '%s'" % algo) return data |