X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=tools%2Fpatman%2Fgitutil.py;h=dce7fa25b6447cf66ccbfc3572efc472e38032cd;hb=46281a76bee3fdb18f75b4c71405ac5cacf1a64e;hp=29e6fdd43b6ce4d9a2307a08494a921967632d1f;hpb=e752edcb6b22b2c42c8064d66a93f0910cac6fef;p=oweals%2Fu-boot.git diff --git a/tools/patman/gitutil.py b/tools/patman/gitutil.py index 29e6fdd43b..dce7fa25b6 100644 --- a/tools/patman/gitutil.py +++ b/tools/patman/gitutil.py @@ -1,7 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2011 The Chromium OS Authors. # -# SPDX-License-Identifier: GPL-2.0+ -# import command import re @@ -13,6 +12,7 @@ import terminal import checkpatch import settings +import tools # True to use --no-decorate - we check this in Setup() use_no_decorate = True @@ -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: @@ -44,6 +44,11 @@ def LogCmd(commit_range, git_dir=None, oneline=False, reverse=False, cmd.append('-n%d' % count) if commit_range: cmd.append(commit_range) + + # Add this in case we have a branch with the same name as a directory. + # This avoids messages like this, for example: + # fatal: ambiguous argument 'test': both revision and filename + cmd.append('--') return cmd def CountCommitsToBranch(): @@ -61,6 +66,52 @@ def CountCommitsToBranch(): patch_count = int(stdout) return patch_count +def NameRevision(commit_hash): + """Gets the revision name for a commit + + Args: + commit_hash: Commit hash to look up + + Return: + Name of revision, if any, else None + """ + pipe = ['git', 'name-rev', commit_hash] + stdout = command.RunPipe([pipe], capture=True, oneline=True).stdout + + # We expect a commit, a space, then a revision name + name = stdout.split(' ')[1].strip() + return name + +def GuessUpstream(git_dir, branch): + """Tries to guess the upstream for a branch + + This lists out top commits on a branch and tries to find a suitable + upstream. It does this by looking for the first commit where + 'git name-rev' returns a plain branch name, with no ! or ^ modifiers. + + Args: + git_dir: Git directory containing repo + branch: Name of branch + + Returns: + Tuple: + Name of upstream branch (e.g. 'upstream/master') or None if none + Warning/error message, or None if none + """ + pipe = [LogCmd(branch, git_dir=git_dir, oneline=True, count=100)] + result = command.RunPipe(pipe, capture=True, capture_stderr=True, + raise_on_error=False) + if result.return_code: + return None, "Branch '%s' not found" % branch + for line in result.stdout.splitlines()[1:]: + commit_hash = line.split(' ')[0] + name = NameRevision(commit_hash) + if '~' not in name and '^' not in name: + if name.startswith('remotes/'): + name = name[8:] + return name, "Guessing upstream as '%s'" % name + return None, "Cannot find a suitable upstream for branch '%s'" % branch + def GetUpstream(git_dir, branch): """Returns the name of the upstream for a branch @@ -69,7 +120,9 @@ def GetUpstream(git_dir, branch): branch: Name of branch Returns: - Name of upstream branch (e.g. 'upstream/master') or None if none + Tuple: + Name of upstream branch (e.g. 'upstream/master') or None if none + Warning/error message, or None if none """ try: remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config', @@ -77,15 +130,16 @@ def GetUpstream(git_dir, branch): merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 'branch.%s.merge' % branch) except: - return None + upstream, msg = GuessUpstream(git_dir, branch) + return upstream, msg if remote == '.': - return merge + return merge, None elif remote and merge: leaf = merge.split('/')[-1] - return '%s/%s' % (remote, leaf) + return '%s/%s' % (remote, leaf), None else: - raise ValueError, ("Cannot determine upstream branch for branch " + raise ValueError("Cannot determine upstream branch for branch " "'%s' remote='%s', merge='%s'" % (branch, remote, merge)) @@ -99,10 +153,29 @@ def GetRangeInBranch(git_dir, branch, include_upstream=False): Expression in the form 'upstream..branch' which can be used to access the commits. If the branch does not exist, returns None. """ - upstream = GetUpstream(git_dir, branch) + upstream, msg = GetUpstream(git_dir, branch) if not upstream: - return None - return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) + return None, msg + rstr = '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) + return rstr, msg + +def CountCommitsInRange(git_dir, range_expr): + """Returns the number of commits in the given range. + + Args: + git_dir: Directory containing git repo + range_expr: Range to check + Return: + Number of patches that exist in the supplied rangem or None if none + were found + """ + pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True)] + result = command.RunPipe(pipe, capture=True, capture_stderr=True, + raise_on_error=False) + if result.return_code: + return None, "Range '%s' not found or is invalid" % range_expr + patch_count = len(result.stdout.splitlines()) + return patch_count, None def CountCommitsInBranch(git_dir, branch, include_upstream=False): """Returns the number of commits in the given branch. @@ -114,14 +187,10 @@ def CountCommitsInBranch(git_dir, branch, include_upstream=False): Number of patches that exist on top of the branch, or None if the branch does not exist. """ - range_expr = GetRangeInBranch(git_dir, branch, include_upstream) + range_expr, msg = GetRangeInBranch(git_dir, branch, include_upstream) if not range_expr: - return None - pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True), - ['wc', '-l']] - result = command.RunPipe(pipe, capture=True, oneline=True) - patch_count = int(result.stdout) - return patch_count + return None, msg + return CountCommitsInRange(git_dir, range_expr) def CountCommits(commit_range): """Returns the number of commits in the given range. @@ -152,9 +221,10 @@ 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) + raise OSError('git checkout (%s): %s' % (pipe, result.stderr)) def Clone(git_dir, output_dir): """Checkout the selected commit for this build @@ -163,9 +233,10 @@ 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 + raise OSError('git clone: %s' % result.stderr) def Fetch(git_dir=None, work_tree=None): """Fetch from the origin repo @@ -179,9 +250,9 @@ 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 + raise OSError('git fetch: %s' % result.stderr) def CreatePatches(start, count, series): """Create a series of patches from the top of the current branch. @@ -215,94 +286,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. @@ -343,6 +326,7 @@ def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): raw += LookupEmail(item, alias, raise_on_error=raise_on_error) result = [] for item in raw: + item = tools.FromUnicode(item) if not item in result: result.append(item) if tag: @@ -350,7 +334,8 @@ def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): return result def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, - self_only=False, alias=None, in_reply_to=None): + self_only=False, alias=None, in_reply_to=None, thread=False, + smtp_server=None): """Email a patch series. Args: @@ -364,6 +349,9 @@ def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, self_only: True to just email to yourself as a test in_reply_to: If set we'll pass this to git as --in-reply-to. Should be a message ID that this is in reply to. + thread: True to add --thread to git send-email (make + all patches reply to cover-letter or first patch in series) + smtp_server: SMTP server to use to send patches Returns: Git command that was/would be run @@ -406,21 +394,27 @@ def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, """ to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error) if not to: - git_config_to = command.Output('git', 'config', 'sendemail.to') + git_config_to = command.Output('git', 'config', 'sendemail.to', + raise_on_error=False) if not git_config_to: - print ("No recipient.\n" - "Please add something like this to a commit\n" - "Series-to: Fred Bloggs \n" - "Or do something like this\n" - "git config sendemail.to u-boot@lists.denx.de") + print("No recipient.\n" + "Please add something like this to a commit\n" + "Series-to: Fred Bloggs \n" + "Or do something like this\n" + "git config sendemail.to u-boot@lists.denx.de") return - cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error) + cc = BuildEmailList(list(set(series.get('cc')) - set(series.get('to'))), + '--cc', alias, raise_on_error) if self_only: to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error) cc = [] cmd = ['git', 'send-email', '--annotate'] + if smtp_server: + cmd.append('--smtp-server=%s' % smtp_server) if in_reply_to: - cmd.append('--in-reply-to="%s"' % in_reply_to) + cmd.append('--in-reply-to="%s"' % tools.FromUnicode(in_reply_to)) + if thread: + cmd.append('--thread') cmd += to cmd += cc @@ -428,10 +422,10 @@ def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, if cover_fname: cmd.append(cover_fname) cmd += args - str = ' '.join(cmd) + cmdstr = ' '.join(cmd) if not dry_run: - os.system(str) - return str + os.system(cmdstr) + return cmdstr def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): @@ -500,18 +494,18 @@ def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): if level > 10: msg = "Recursive email alias at '%s'" % lookup_name if raise_on_error: - raise OSError, msg + raise OSError(msg) else: - print col.Color(col.RED, msg) + print(col.Color(col.RED, msg)) return out_list if lookup_name: if not lookup_name in alias: msg = "Alias '%s' not found" % lookup_name if raise_on_error: - raise ValueError, msg + raise ValueError(msg) else: - print col.Color(col.RED, msg) + print(col.Color(col.RED, msg)) return out_list for item in alias[lookup_name]: todo = LookupEmail(item, alias, raise_on_error, level + 1) @@ -519,7 +513,7 @@ def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): if not new_item in out_list: out_list.append(new_item) - #print "No match for alias '%s'" % lookup_name + #print("No match for alias '%s'" % lookup_name) return out_list def GetTopLevel(): @@ -566,9 +560,22 @@ def GetDefaultUserEmail(): uemail = command.OutputOneLine('git', 'config', '--global', 'user.email') return uemail +def GetDefaultSubjectPrefix(): + """Gets the format.subjectprefix from local .git/config file. + + Returns: + Subject prefix found in local .git/config file, or None if none + """ + sub_prefix = command.OutputOneLine('git', 'config', 'format.subjectprefix', + raise_on_error=False) + + return sub_prefix + 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)