diff options
Diffstat (limited to 'tools/patman')
-rw-r--r-- | tools/patman/command.py | 22 | ||||
-rw-r--r-- | tools/patman/gitutil.py | 108 | ||||
-rw-r--r-- | tools/patman/patchstream.py | 27 | ||||
-rwxr-xr-x | tools/patman/patman.py | 14 | ||||
-rw-r--r-- | tools/patman/terminal.py | 178 | ||||
-rw-r--r-- | tools/patman/test.py | 13 |
6 files changed, 186 insertions, 176 deletions
diff --git a/tools/patman/command.py b/tools/patman/command.py index 449d3d0..d586f11 100644 --- a/tools/patman/command.py +++ b/tools/patman/command.py @@ -20,9 +20,25 @@ class CommandResult: def __init__(self): self.stdout = None self.stderr = None + self.combined = None self.return_code = None self.exception = None + def __init__(self, stdout='', stderr='', combined='', return_code=0, + exception=None): + self.stdout = stdout + self.stderr = stderr + self.combined = combined + self.return_code = return_code + self.exception = exception + + +# This permits interception of RunPipe for test purposes. If it is set to +# a function, then that function is called with the pipe list being +# executed. Otherwise, it is assumed to be a CommandResult object, and is +# returned as the result for every RunPipe() call. +# When this value is None, commands are executed as normal. +test_result = None def RunPipe(pipe_list, infile=None, outfile=None, capture=False, capture_stderr=False, oneline=False, @@ -44,10 +60,16 @@ def RunPipe(pipe_list, infile=None, outfile=None, Returns: CommandResult object """ + if test_result: + if hasattr(test_result, '__call__'): + return test_result(pipe_list=pipe_list) + return test_result result = CommandResult() last_pipe = None pipeline = list(pipe_list) user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list]) + kwargs['stdout'] = None + kwargs['stderr'] = None while pipeline: cmd = pipeline.pop(0) if last_pipe is not None: diff --git a/tools/patman/gitutil.py b/tools/patman/gitutil.py index e2b4959..b68df5d 100644 --- a/tools/patman/gitutil.py +++ b/tools/patman/gitutil.py @@ -33,7 +33,7 @@ def LogCmd(commit_range, git_dir=None, oneline=False, reverse=False, cmd = ['git'] if git_dir: cmd += ['--git-dir', git_dir] - cmd += ['log', '--no-color'] + cmd += ['--no-pager', 'log', '--no-color'] if oneline: cmd.append('--oneline') if use_no_decorate: @@ -152,7 +152,8 @@ def Checkout(commit_hash, git_dir=None, work_tree=None, force=False): if force: pipe.append('-f') pipe.append(commit_hash) - result = command.RunPipe([pipe], capture=True, raise_on_error=False) + result = command.RunPipe([pipe], capture=True, raise_on_error=False, + capture_stderr=True) if result.return_code != 0: raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr) @@ -163,7 +164,8 @@ def Clone(git_dir, output_dir): commit_hash: Commit hash to check out """ pipe = ['git', 'clone', git_dir, '.'] - result = command.RunPipe([pipe], capture=True, cwd=output_dir) + result = command.RunPipe([pipe], capture=True, cwd=output_dir, + capture_stderr=True) if result.return_code != 0: raise OSError, 'git clone: %s' % result.stderr @@ -179,7 +181,7 @@ def Fetch(git_dir=None, work_tree=None): if work_tree: pipe.extend(['--work-tree', work_tree]) pipe.append('fetch') - result = command.RunPipe([pipe], capture=True) + result = command.RunPipe([pipe], capture=True, capture_stderr=True) if result.return_code != 0: raise OSError, 'git fetch: %s' % result.stderr @@ -215,94 +217,6 @@ def CreatePatches(start, count, series): else: return None, files -def ApplyPatch(verbose, fname): - """Apply a patch with git am to test it - - TODO: Convert these to use command, with stderr option - - Args: - fname: filename of patch file to apply - """ - col = terminal.Color() - cmd = ['git', 'am', fname] - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = pipe.communicate() - re_error = re.compile('^error: patch failed: (.+):(\d+)') - for line in stderr.splitlines(): - if verbose: - print line - match = re_error.match(line) - if match: - print checkpatch.GetWarningMsg(col, 'warning', match.group(1), - int(match.group(2)), 'Patch failed') - return pipe.returncode == 0, stdout - -def ApplyPatches(verbose, args, start_point): - """Apply the patches with git am to make sure all is well - - Args: - verbose: Print out 'git am' output verbatim - args: List of patch files to apply - start_point: Number of commits back from HEAD to start applying. - Normally this is len(args), but it can be larger if a start - offset was given. - """ - error_count = 0 - col = terminal.Color() - - # Figure out our current position - cmd = ['git', 'name-rev', 'HEAD', '--name-only'] - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) - stdout, stderr = pipe.communicate() - if pipe.returncode: - str = 'Could not find current commit name' - print col.Color(col.RED, str) - print stdout - return False - old_head = stdout.splitlines()[0] - if old_head == 'undefined': - str = "Invalid HEAD '%s'" % stdout.strip() - print col.Color(col.RED, str) - return False - - # Checkout the required start point - cmd = ['git', 'checkout', 'HEAD~%d' % start_point] - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = pipe.communicate() - if pipe.returncode: - str = 'Could not move to commit before patch series' - print col.Color(col.RED, str) - print stdout, stderr - return False - - # Apply all the patches - for fname in args: - ok, stdout = ApplyPatch(verbose, fname) - if not ok: - print col.Color(col.RED, 'git am returned errors for %s: will ' - 'skip this patch' % fname) - if verbose: - print stdout - error_count += 1 - cmd = ['git', 'am', '--skip'] - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) - stdout, stderr = pipe.communicate() - if pipe.returncode != 0: - print col.Color(col.RED, 'Unable to skip patch! Aborting...') - print stdout - break - - # Return to our previous position - cmd = ['git', 'checkout', old_head] - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = pipe.communicate() - if pipe.returncode: - print col.Color(col.RED, 'Could not move back to head commit') - print stdout, stderr - return error_count == 0 - def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): """Build a list of email addresses based on an input list. @@ -478,13 +392,13 @@ def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): ... OSError: Recursive email alias at 'other' >>> LookupEmail('odd', alias, raise_on_error=False) - \033[1;31mAlias 'odd' not found\033[0m + Alias 'odd' not found [] >>> # In this case the loop part will effectively be ignored. >>> LookupEmail('loop', alias, raise_on_error=False) - \033[1;31mRecursive email alias at 'other'\033[0m - \033[1;31mRecursive email alias at 'john'\033[0m - \033[1;31mRecursive email alias at 'mary'\033[0m + Recursive email alias at 'other' + Recursive email alias at 'john' + Recursive email alias at 'mary' ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] """ if not alias: @@ -569,6 +483,8 @@ def GetDefaultUserEmail(): def Setup(): """Set up git utils, by reading the alias files.""" # Check for a git alias file also + global use_no_decorate + alias_fname = GetAliasFile() if alias_fname: settings.ReadGitAliases(alias_fname) diff --git a/tools/patman/patchstream.py b/tools/patman/patchstream.py index 0040468..d630157 100644 --- a/tools/patman/patchstream.py +++ b/tools/patman/patchstream.py @@ -72,7 +72,6 @@ class PatchStream: self.in_change = 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.tags = [] # Tags collected, like Tested-by... self.signoff = [] # Contents of signoff line self.commit = None # Current commit @@ -113,16 +112,6 @@ class PatchStream: self.series.AddCommit(self.commit) self.commit = None - def FormatTags(self, tags): - out_list = [] - for tag in sorted(tags): - if tag.startswith('Cc:'): - tag_list = tag[4:].split(',') - out_list += gitutil.BuildEmailList(tag_list, 'Cc:') - else: - out_list.append(tag) - return out_list - def ProcessLine(self, line): """Process a single line of a patch file or commit log @@ -271,11 +260,11 @@ class PatchStream: elif tag_match.group(1) == 'Patch-cc': self.commit.AddCc(tag_match.group(2).split(',')) else: - self.tags.append(line); + out = [line] # Suppress duplicate signoffs elif signoff_match: - if (self.is_log or + if (self.is_log or not self.commit or self.commit.CheckDuplicateSignoff(signoff_match.group(1))): out = [line] @@ -311,8 +300,10 @@ class PatchStream: # Output the tags (signeoff first), then change list out = [] log = self.series.MakeChangeLog(self.commit) - out += self.FormatTags(self.tags) - out += [line] + self.commit.notes + [''] + log + out += [line] + if self.commit: + out += self.commit.notes + out += [''] + log elif self.found_test: if not re_allowed_after_test.match(line): self.lines_after_test += 1 @@ -364,7 +355,7 @@ class PatchStream: def GetMetaDataForList(commit_range, git_dir=None, count=None, - series = Series()): + series = None, allow_overwrite=False): """Reads out patch series metadata from the commits This does a 'git log' on the relevant commits and pulls out the tags we @@ -376,9 +367,13 @@ def GetMetaDataForList(commit_range, git_dir=None, count=None, count: Number of commits to list, or None for no limit series: Series object to add information into. By default a new series is started. + allow_overwrite: Allow tags to overwrite an existing tag Returns: A Series object containing information about the commits. """ + if not series: + series = Series() + series.allow_overwrite = allow_overwrite params = gitutil.LogCmd(commit_range,reverse=True, count=count, git_dir=git_dir) stdout = command.RunPipe([params], capture=True).stdout diff --git a/tools/patman/patman.py b/tools/patman/patman.py index ca34cb9..2ab6b35 100755 --- a/tools/patman/patman.py +++ b/tools/patman/patman.py @@ -25,9 +25,6 @@ import test parser = OptionParser() -parser.add_option('-a', '--no-apply', action='store_false', - dest='apply_patches', default=True, - help="Don't test-apply patches with git am") parser.add_option('-H', '--full-help', action='store_true', dest='full_help', default=False, help='Display the README file') parser.add_option('-c', '--count', dest='count', type='int', @@ -143,23 +140,24 @@ else: ok = checkpatch.CheckPatches(options.verbose, args) else: ok = True - if options.apply_patches: - if not gitutil.ApplyPatches(options.verbose, args, - options.count + options.start): - ok = False cc_file = series.MakeCcFile(options.process_tags, cover_fname, not options.ignore_bad_tags) # Email the patches out (giving the user time to check / cancel) cmd = '' - if ok or options.ignore_errors: + its_a_go = ok or options.ignore_errors + if its_a_go: cmd = gitutil.EmailPatches(series, cover_fname, args, options.dry_run, not options.ignore_bad_tags, cc_file, in_reply_to=options.in_reply_to) + else: + print col.Color(col.RED, "Not sending emails due to errors/warnings") # For a dry run, just show our actions as a sanity check if options.dry_run: series.ShowActions(args, cmd, options.process_tags) + if not its_a_go: + print col.Color(col.RED, "Email would not be sent") os.remove(cc_file) diff --git a/tools/patman/terminal.py b/tools/patman/terminal.py index 597d526..e78a7c1 100644 --- a/tools/patman/terminal.py +++ b/tools/patman/terminal.py @@ -14,67 +14,145 @@ import sys # Selection of when we want our output to be colored COLOR_IF_TERMINAL, COLOR_ALWAYS, COLOR_NEVER = range(3) -class Color(object): - """Conditionally wraps text in ANSI color escape sequences.""" - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) - BOLD = -1 - BRIGHT_START = '\033[1;%dm' - NORMAL_START = '\033[22;%dm' - BOLD_START = '\033[1m' - RESET = '\033[0m' +# Initially, we are set up to print to the terminal +print_test_mode = False +print_test_list = [] - def __init__(self, colored=COLOR_IF_TERMINAL): - """Create a new Color object, optionally disabling color output. +class PrintLine: + """A line of text output - Args: - enabled: True if color output should be enabled. If False then this - class will not add color codes at all. + Members: + text: Text line that was printed + newline: True to output a newline after the text + colour: Text colour to use """ - self._enabled = (colored == COLOR_ALWAYS or - (colored == COLOR_IF_TERMINAL and os.isatty(sys.stdout.fileno()))) + def __init__(self, text, newline, colour): + self.text = text + self.newline = newline + self.colour = colour - def Start(self, color, bright=True): - """Returns a start color code. + def __str__(self): + return 'newline=%s, colour=%s, text=%s' % (self.newline, self.colour, + self.text) - Args: - color: Color to use, .e.g BLACK, RED, etc. +def Print(text='', newline=True, colour=None): + """Handle a line of output to the terminal. - Returns: - If color is enabled, returns an ANSI sequence to start the given color, - otherwise returns empty string + In test mode this is recorded in a list. Otherwise it is output to the + terminal. + + Args: + text: Text to print + newline: True to add a new line at the end of the text + colour: Colour to use for the text """ - if self._enabled: - base = self.BRIGHT_START if bright else self.NORMAL_START - return base % (color + 30) - return '' + if print_test_mode: + print_test_list.append(PrintLine(text, newline, colour)) + else: + if colour: + col = Color() + text = col.Color(colour, text) + print text, + if newline: + print + +def SetPrintTestMode(): + """Go into test mode, where all printing is recorded""" + global print_test_mode + + print_test_mode = True - def Stop(self): - """Retruns a stop color code. +def GetPrintTestLines(): + """Get a list of all lines output through Print() Returns: - If color is enabled, returns an ANSI color reset sequence, otherwise - returns empty string + A list of PrintLine objects """ - if self._enabled: - return self.RESET - return '' + global print_test_list - def Color(self, color, text, bright=True): - """Returns text with conditionally added color escape sequences. + ret = print_test_list + print_test_list = [] + return ret - Keyword arguments: - color: Text color -- one of the color constants defined in this class. - text: The text to color. +def EchoPrintTestLines(): + """Print out the text lines collected""" + for line in print_test_list: + if line.colour: + col = Color() + print col.Color(line.colour, line.text), + else: + print line.text, + if line.newline: + print - Returns: - If self._enabled is False, returns the original text. If it's True, - returns text with color escape sequences based on the value of color. - """ - if not self._enabled: - return text - if color == self.BOLD: - start = self.BOLD_START - else: - base = self.BRIGHT_START if bright else self.NORMAL_START - start = base % (color + 30) - return start + text + self.RESET + +class Color(object): + """Conditionally wraps text in ANSI color escape sequences.""" + BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + BOLD = -1 + BRIGHT_START = '\033[1;%dm' + NORMAL_START = '\033[22;%dm' + BOLD_START = '\033[1m' + RESET = '\033[0m' + + def __init__(self, colored=COLOR_IF_TERMINAL): + """Create a new Color object, optionally disabling color output. + + Args: + enabled: True if color output should be enabled. If False then this + class will not add color codes at all. + """ + try: + self._enabled = (colored == COLOR_ALWAYS or + (colored == COLOR_IF_TERMINAL and + os.isatty(sys.stdout.fileno()))) + except: + self._enabled = False + + def Start(self, color, bright=True): + """Returns a start color code. + + Args: + color: Color to use, .e.g BLACK, RED, etc. + + Returns: + If color is enabled, returns an ANSI sequence to start the given + color, otherwise returns empty string + """ + if self._enabled: + base = self.BRIGHT_START if bright else self.NORMAL_START + return base % (color + 30) + return '' + + def Stop(self): + """Retruns a stop color code. + + Returns: + If color is enabled, returns an ANSI color reset sequence, + otherwise returns empty string + """ + if self._enabled: + return self.RESET + return '' + + def Color(self, color, text, bright=True): + """Returns text with conditionally added color escape sequences. + + Keyword arguments: + color: Text color -- one of the color constants defined in this + class. + text: The text to color. + + Returns: + If self._enabled is False, returns the original text. If it's True, + returns text with color escape sequences based on the value of + color. + """ + if not self._enabled: + return text + if color == self.BOLD: + start = self.BOLD_START + else: + base = self.BRIGHT_START if bright else self.NORMAL_START + start = base % (color + 30) + return start + text + self.RESET diff --git a/tools/patman/test.py b/tools/patman/test.py index 8fcfe53..e8f7472 100644 --- a/tools/patman/test.py +++ b/tools/patman/test.py @@ -55,6 +55,7 @@ This adds functions to enable/disable clocks and reset to on-chip peripherals. Signed-off-by: Simon Glass <sjg@chromium.org> --- + arch/arm/cpu/armv7/tegra2/Makefile | 2 +- arch/arm/cpu/armv7/tegra2/ap20.c | 57 ++---- arch/arm/cpu/armv7/tegra2/clock.c | 163 +++++++++++++++++ @@ -200,7 +201,7 @@ index 0000000..2234c87 self.assertEqual(result.errors, 0) self.assertEqual(result.warnings, 0) self.assertEqual(result.checks, 0) - self.assertEqual(result.lines, 67) + self.assertEqual(result.lines, 56) os.remove(inf) def testNoSignoff(self): @@ -211,18 +212,18 @@ index 0000000..2234c87 self.assertEqual(result.errors, 1) self.assertEqual(result.warnings, 0) self.assertEqual(result.checks, 0) - self.assertEqual(result.lines, 67) + self.assertEqual(result.lines, 56) os.remove(inf) def testSpaces(self): inf = self.SetupData('spaces') result = checkpatch.CheckPatch(inf) self.assertEqual(result.ok, False) - self.assertEqual(len(result.problems), 1) + self.assertEqual(len(result.problems), 2) self.assertEqual(result.errors, 0) - self.assertEqual(result.warnings, 1) + self.assertEqual(result.warnings, 2) self.assertEqual(result.checks, 0) - self.assertEqual(result.lines, 67) + self.assertEqual(result.lines, 56) os.remove(inf) def testIndent(self): @@ -233,7 +234,7 @@ index 0000000..2234c87 self.assertEqual(result.errors, 0) self.assertEqual(result.warnings, 0) self.assertEqual(result.checks, 1) - self.assertEqual(result.lines, 67) + self.assertEqual(result.lines, 56) os.remove(inf) |