10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors. 20d24de9dSSimon Glass# 31a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+ 40d24de9dSSimon Glass# 50d24de9dSSimon Glass 60d24de9dSSimon Glassimport command 70d24de9dSSimon Glassimport re 80d24de9dSSimon Glassimport os 90d24de9dSSimon Glassimport series 100d24de9dSSimon Glassimport subprocess 110d24de9dSSimon Glassimport sys 120d24de9dSSimon Glassimport terminal 130d24de9dSSimon Glass 14757f64a8SSimon Glassimport checkpatch 155f6a1c42SSimon Glassimport settings 165f6a1c42SSimon Glass 17e49f14afSSimon Glass# True to use --no-decorate - we check this in Setup() 18e49f14afSSimon Glassuse_no_decorate = True 19e49f14afSSimon Glass 20cda2a611SSimon Glassdef LogCmd(commit_range, git_dir=None, oneline=False, reverse=False, 21cda2a611SSimon Glass count=None): 22cda2a611SSimon Glass """Create a command to perform a 'git log' 23cda2a611SSimon Glass 24cda2a611SSimon Glass Args: 25cda2a611SSimon Glass commit_range: Range expression to use for log, None for none 26cda2a611SSimon Glass git_dir: Path to git repositiory (None to use default) 27cda2a611SSimon Glass oneline: True to use --oneline, else False 28cda2a611SSimon Glass reverse: True to reverse the log (--reverse) 29cda2a611SSimon Glass count: Number of commits to list, or None for no limit 30cda2a611SSimon Glass Return: 31cda2a611SSimon Glass List containing command and arguments to run 32cda2a611SSimon Glass """ 33cda2a611SSimon Glass cmd = ['git'] 34cda2a611SSimon Glass if git_dir: 35cda2a611SSimon Glass cmd += ['--git-dir', git_dir] 369447a6b2SSimon Glass cmd += ['--no-pager', 'log', '--no-color'] 37cda2a611SSimon Glass if oneline: 38cda2a611SSimon Glass cmd.append('--oneline') 39e49f14afSSimon Glass if use_no_decorate: 40cda2a611SSimon Glass cmd.append('--no-decorate') 41042a732cSSimon Glass if reverse: 42042a732cSSimon Glass cmd.append('--reverse') 43cda2a611SSimon Glass if count is not None: 44cda2a611SSimon Glass cmd.append('-n%d' % count) 45cda2a611SSimon Glass if commit_range: 46cda2a611SSimon Glass cmd.append(commit_range) 47cda2a611SSimon Glass return cmd 480d24de9dSSimon Glass 490d24de9dSSimon Glassdef CountCommitsToBranch(): 500d24de9dSSimon Glass """Returns number of commits between HEAD and the tracking branch. 510d24de9dSSimon Glass 520d24de9dSSimon Glass This looks back to the tracking branch and works out the number of commits 530d24de9dSSimon Glass since then. 540d24de9dSSimon Glass 550d24de9dSSimon Glass Return: 560d24de9dSSimon Glass Number of patches that exist on top of the branch 570d24de9dSSimon Glass """ 58cda2a611SSimon Glass pipe = [LogCmd('@{upstream}..', oneline=True), 590d24de9dSSimon Glass ['wc', '-l']] 60a10fd93cSSimon Glass stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout 610d24de9dSSimon Glass patch_count = int(stdout) 620d24de9dSSimon Glass return patch_count 630d24de9dSSimon Glass 64*2a9e2c6aSSimon Glassdef NameRevision(commit_hash): 65*2a9e2c6aSSimon Glass """Gets the revision name for a commit 66*2a9e2c6aSSimon Glass 67*2a9e2c6aSSimon Glass Args: 68*2a9e2c6aSSimon Glass commit_hash: Commit hash to look up 69*2a9e2c6aSSimon Glass 70*2a9e2c6aSSimon Glass Return: 71*2a9e2c6aSSimon Glass Name of revision, if any, else None 72*2a9e2c6aSSimon Glass """ 73*2a9e2c6aSSimon Glass pipe = ['git', 'name-rev', commit_hash] 74*2a9e2c6aSSimon Glass stdout = command.RunPipe([pipe], capture=True, oneline=True).stdout 75*2a9e2c6aSSimon Glass 76*2a9e2c6aSSimon Glass # We expect a commit, a space, then a revision name 77*2a9e2c6aSSimon Glass name = stdout.split(' ')[1].strip() 78*2a9e2c6aSSimon Glass return name 79*2a9e2c6aSSimon Glass 80*2a9e2c6aSSimon Glassdef GuessUpstream(git_dir, branch): 81*2a9e2c6aSSimon Glass """Tries to guess the upstream for a branch 82*2a9e2c6aSSimon Glass 83*2a9e2c6aSSimon Glass This lists out top commits on a branch and tries to find a suitable 84*2a9e2c6aSSimon Glass upstream. It does this by looking for the first commit where 85*2a9e2c6aSSimon Glass 'git name-rev' returns a plain branch name, with no ! or ^ modifiers. 86*2a9e2c6aSSimon Glass 87*2a9e2c6aSSimon Glass Args: 88*2a9e2c6aSSimon Glass git_dir: Git directory containing repo 89*2a9e2c6aSSimon Glass branch: Name of branch 90*2a9e2c6aSSimon Glass 91*2a9e2c6aSSimon Glass Returns: 92*2a9e2c6aSSimon Glass Tuple: 93*2a9e2c6aSSimon Glass Name of upstream branch (e.g. 'upstream/master') or None if none 94*2a9e2c6aSSimon Glass Warning/error message, or None if none 95*2a9e2c6aSSimon Glass """ 96*2a9e2c6aSSimon Glass pipe = [LogCmd(branch, git_dir=git_dir, oneline=True, count=100)] 97*2a9e2c6aSSimon Glass result = command.RunPipe(pipe, capture=True, capture_stderr=True, 98*2a9e2c6aSSimon Glass raise_on_error=False) 99*2a9e2c6aSSimon Glass if result.return_code: 100*2a9e2c6aSSimon Glass return None, "Branch '%s' not found" % branch 101*2a9e2c6aSSimon Glass for line in result.stdout.splitlines()[1:]: 102*2a9e2c6aSSimon Glass commit_hash = line.split(' ')[0] 103*2a9e2c6aSSimon Glass name = NameRevision(commit_hash) 104*2a9e2c6aSSimon Glass if '~' not in name and '^' not in name: 105*2a9e2c6aSSimon Glass if name.startswith('remotes/'): 106*2a9e2c6aSSimon Glass name = name[8:] 107*2a9e2c6aSSimon Glass return name, "Guessing upstream as '%s'" % name 108*2a9e2c6aSSimon Glass return None, "Cannot find a suitable upstream for branch '%s'" % branch 109*2a9e2c6aSSimon Glass 1105f6a1c42SSimon Glassdef GetUpstream(git_dir, branch): 1115f6a1c42SSimon Glass """Returns the name of the upstream for a branch 1125f6a1c42SSimon Glass 1135f6a1c42SSimon Glass Args: 1145f6a1c42SSimon Glass git_dir: Git directory containing repo 1155f6a1c42SSimon Glass branch: Name of branch 1165f6a1c42SSimon Glass 1175f6a1c42SSimon Glass Returns: 118*2a9e2c6aSSimon Glass Tuple: 1195f6a1c42SSimon Glass Name of upstream branch (e.g. 'upstream/master') or None if none 120*2a9e2c6aSSimon Glass Warning/error message, or None if none 1215f6a1c42SSimon Glass """ 122cce717a9SSimon Glass try: 1235f6a1c42SSimon Glass remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 1245f6a1c42SSimon Glass 'branch.%s.remote' % branch) 1255f6a1c42SSimon Glass merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 1265f6a1c42SSimon Glass 'branch.%s.merge' % branch) 127cce717a9SSimon Glass except: 128*2a9e2c6aSSimon Glass upstream, msg = GuessUpstream(git_dir, branch) 129*2a9e2c6aSSimon Glass return upstream, msg 130cce717a9SSimon Glass 1315f6a1c42SSimon Glass if remote == '.': 1325f6a1c42SSimon Glass return merge 1335f6a1c42SSimon Glass elif remote and merge: 1345f6a1c42SSimon Glass leaf = merge.split('/')[-1] 135*2a9e2c6aSSimon Glass return '%s/%s' % (remote, leaf), None 1365f6a1c42SSimon Glass else: 1375f6a1c42SSimon Glass raise ValueError, ("Cannot determine upstream branch for branch " 1385f6a1c42SSimon Glass "'%s' remote='%s', merge='%s'" % (branch, remote, merge)) 1395f6a1c42SSimon Glass 1405f6a1c42SSimon Glass 1415f6a1c42SSimon Glassdef GetRangeInBranch(git_dir, branch, include_upstream=False): 1425f6a1c42SSimon Glass """Returns an expression for the commits in the given branch. 1435f6a1c42SSimon Glass 1445f6a1c42SSimon Glass Args: 1455f6a1c42SSimon Glass git_dir: Directory containing git repo 1465f6a1c42SSimon Glass branch: Name of branch 1475f6a1c42SSimon Glass Return: 1485f6a1c42SSimon Glass Expression in the form 'upstream..branch' which can be used to 149cce717a9SSimon Glass access the commits. If the branch does not exist, returns None. 1505f6a1c42SSimon Glass """ 151*2a9e2c6aSSimon Glass upstream, msg = GetUpstream(git_dir, branch) 152cce717a9SSimon Glass if not upstream: 153*2a9e2c6aSSimon Glass return None, msg 154*2a9e2c6aSSimon Glass rstr = '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) 155*2a9e2c6aSSimon Glass return rstr, msg 1565f6a1c42SSimon Glass 1575f6a1c42SSimon Glassdef CountCommitsInBranch(git_dir, branch, include_upstream=False): 1585f6a1c42SSimon Glass """Returns the number of commits in the given branch. 1595f6a1c42SSimon Glass 1605f6a1c42SSimon Glass Args: 1615f6a1c42SSimon Glass git_dir: Directory containing git repo 1625f6a1c42SSimon Glass branch: Name of branch 1635f6a1c42SSimon Glass Return: 164cce717a9SSimon Glass Number of patches that exist on top of the branch, or None if the 165cce717a9SSimon Glass branch does not exist. 1665f6a1c42SSimon Glass """ 167*2a9e2c6aSSimon Glass range_expr, msg = GetRangeInBranch(git_dir, branch, include_upstream) 168cce717a9SSimon Glass if not range_expr: 169*2a9e2c6aSSimon Glass return None, msg 170cda2a611SSimon Glass pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True), 1715f6a1c42SSimon Glass ['wc', '-l']] 1725f6a1c42SSimon Glass result = command.RunPipe(pipe, capture=True, oneline=True) 1735f6a1c42SSimon Glass patch_count = int(result.stdout) 174*2a9e2c6aSSimon Glass return patch_count, msg 1755f6a1c42SSimon Glass 1765f6a1c42SSimon Glassdef CountCommits(commit_range): 1775f6a1c42SSimon Glass """Returns the number of commits in the given range. 1785f6a1c42SSimon Glass 1795f6a1c42SSimon Glass Args: 1805f6a1c42SSimon Glass commit_range: Range of commits to count (e.g. 'HEAD..base') 1815f6a1c42SSimon Glass Return: 1825f6a1c42SSimon Glass Number of patches that exist on top of the branch 1835f6a1c42SSimon Glass """ 184cda2a611SSimon Glass pipe = [LogCmd(commit_range, oneline=True), 1855f6a1c42SSimon Glass ['wc', '-l']] 1865f6a1c42SSimon Glass stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout 1875f6a1c42SSimon Glass patch_count = int(stdout) 1885f6a1c42SSimon Glass return patch_count 1895f6a1c42SSimon Glass 1905f6a1c42SSimon Glassdef Checkout(commit_hash, git_dir=None, work_tree=None, force=False): 1915f6a1c42SSimon Glass """Checkout the selected commit for this build 1925f6a1c42SSimon Glass 1935f6a1c42SSimon Glass Args: 1945f6a1c42SSimon Glass commit_hash: Commit hash to check out 1955f6a1c42SSimon Glass """ 1965f6a1c42SSimon Glass pipe = ['git'] 1975f6a1c42SSimon Glass if git_dir: 1985f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 1995f6a1c42SSimon Glass if work_tree: 2005f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 2015f6a1c42SSimon Glass pipe.append('checkout') 2025f6a1c42SSimon Glass if force: 2035f6a1c42SSimon Glass pipe.append('-f') 2045f6a1c42SSimon Glass pipe.append(commit_hash) 205ddaf5c8fSSimon Glass result = command.RunPipe([pipe], capture=True, raise_on_error=False, 206ddaf5c8fSSimon Glass capture_stderr=True) 2075f6a1c42SSimon Glass if result.return_code != 0: 2085f6a1c42SSimon Glass raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr) 2095f6a1c42SSimon Glass 2105f6a1c42SSimon Glassdef Clone(git_dir, output_dir): 2115f6a1c42SSimon Glass """Checkout the selected commit for this build 2125f6a1c42SSimon Glass 2135f6a1c42SSimon Glass Args: 2145f6a1c42SSimon Glass commit_hash: Commit hash to check out 2155f6a1c42SSimon Glass """ 2165f6a1c42SSimon Glass pipe = ['git', 'clone', git_dir, '.'] 217ddaf5c8fSSimon Glass result = command.RunPipe([pipe], capture=True, cwd=output_dir, 218ddaf5c8fSSimon Glass capture_stderr=True) 2195f6a1c42SSimon Glass if result.return_code != 0: 2205f6a1c42SSimon Glass raise OSError, 'git clone: %s' % result.stderr 2215f6a1c42SSimon Glass 2225f6a1c42SSimon Glassdef Fetch(git_dir=None, work_tree=None): 2235f6a1c42SSimon Glass """Fetch from the origin repo 2245f6a1c42SSimon Glass 2255f6a1c42SSimon Glass Args: 2265f6a1c42SSimon Glass commit_hash: Commit hash to check out 2275f6a1c42SSimon Glass """ 2285f6a1c42SSimon Glass pipe = ['git'] 2295f6a1c42SSimon Glass if git_dir: 2305f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 2315f6a1c42SSimon Glass if work_tree: 2325f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 2335f6a1c42SSimon Glass pipe.append('fetch') 234ddaf5c8fSSimon Glass result = command.RunPipe([pipe], capture=True, capture_stderr=True) 2355f6a1c42SSimon Glass if result.return_code != 0: 2365f6a1c42SSimon Glass raise OSError, 'git fetch: %s' % result.stderr 2375f6a1c42SSimon Glass 2380d24de9dSSimon Glassdef CreatePatches(start, count, series): 2390d24de9dSSimon Glass """Create a series of patches from the top of the current branch. 2400d24de9dSSimon Glass 2410d24de9dSSimon Glass The patch files are written to the current directory using 2420d24de9dSSimon Glass git format-patch. 2430d24de9dSSimon Glass 2440d24de9dSSimon Glass Args: 2450d24de9dSSimon Glass start: Commit to start from: 0=HEAD, 1=next one, etc. 2460d24de9dSSimon Glass count: number of commits to include 2470d24de9dSSimon Glass Return: 2480d24de9dSSimon Glass Filename of cover letter 2490d24de9dSSimon Glass List of filenames of patch files 2500d24de9dSSimon Glass """ 2510d24de9dSSimon Glass if series.get('version'): 2520d24de9dSSimon Glass version = '%s ' % series['version'] 2530d24de9dSSimon Glass cmd = ['git', 'format-patch', '-M', '--signoff'] 2540d24de9dSSimon Glass if series.get('cover'): 2550d24de9dSSimon Glass cmd.append('--cover-letter') 2560d24de9dSSimon Glass prefix = series.GetPatchPrefix() 2570d24de9dSSimon Glass if prefix: 2580d24de9dSSimon Glass cmd += ['--subject-prefix=%s' % prefix] 2590d24de9dSSimon Glass cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)] 2600d24de9dSSimon Glass 2610d24de9dSSimon Glass stdout = command.RunList(cmd) 2620d24de9dSSimon Glass files = stdout.splitlines() 2630d24de9dSSimon Glass 2640d24de9dSSimon Glass # We have an extra file if there is a cover letter 2650d24de9dSSimon Glass if series.get('cover'): 2660d24de9dSSimon Glass return files[0], files[1:] 2670d24de9dSSimon Glass else: 2680d24de9dSSimon Glass return None, files 2690d24de9dSSimon Glass 270a1318f7cSSimon Glassdef BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): 2710d24de9dSSimon Glass """Build a list of email addresses based on an input list. 2720d24de9dSSimon Glass 2730d24de9dSSimon Glass Takes a list of email addresses and aliases, and turns this into a list 2740d24de9dSSimon Glass of only email address, by resolving any aliases that are present. 2750d24de9dSSimon Glass 2760d24de9dSSimon Glass If the tag is given, then each email address is prepended with this 2770d24de9dSSimon Glass tag and a space. If the tag starts with a minus sign (indicating a 2780d24de9dSSimon Glass command line parameter) then the email address is quoted. 2790d24de9dSSimon Glass 2800d24de9dSSimon Glass Args: 2810d24de9dSSimon Glass in_list: List of aliases/email addresses 2820d24de9dSSimon Glass tag: Text to put before each address 283a1318f7cSSimon Glass alias: Alias dictionary 284a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 285a1318f7cSSimon Glass False to just print a message. 2860d24de9dSSimon Glass 2870d24de9dSSimon Glass Returns: 2880d24de9dSSimon Glass List of email addresses 2890d24de9dSSimon Glass 2900d24de9dSSimon Glass >>> alias = {} 2910d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 2920d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 2930d24de9dSSimon Glass >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>'] 2940d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 2950d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 2960d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], None, alias) 2970d24de9dSSimon Glass ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>'] 2980d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], '--to', alias) 2990d24de9dSSimon Glass ['--to "j.bloggs@napier.co.nz"', \ 3000d24de9dSSimon Glass'--to "Mary Poppins <m.poppins@cloud.net>"'] 3010d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], 'Cc', alias) 3020d24de9dSSimon Glass ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>'] 3030d24de9dSSimon Glass """ 3040d24de9dSSimon Glass quote = '"' if tag and tag[0] == '-' else '' 3050d24de9dSSimon Glass raw = [] 3060d24de9dSSimon Glass for item in in_list: 307a1318f7cSSimon Glass raw += LookupEmail(item, alias, raise_on_error=raise_on_error) 3080d24de9dSSimon Glass result = [] 3090d24de9dSSimon Glass for item in raw: 3100d24de9dSSimon Glass if not item in result: 3110d24de9dSSimon Glass result.append(item) 3120d24de9dSSimon Glass if tag: 3130d24de9dSSimon Glass return ['%s %s%s%s' % (tag, quote, email, quote) for email in result] 3140d24de9dSSimon Glass return result 3150d24de9dSSimon Glass 316a1318f7cSSimon Glassdef EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, 3176d819925SDoug Anderson self_only=False, alias=None, in_reply_to=None): 3180d24de9dSSimon Glass """Email a patch series. 3190d24de9dSSimon Glass 3200d24de9dSSimon Glass Args: 3210d24de9dSSimon Glass series: Series object containing destination info 3220d24de9dSSimon Glass cover_fname: filename of cover letter 3230d24de9dSSimon Glass args: list of filenames of patch files 3240d24de9dSSimon Glass dry_run: Just return the command that would be run 325a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 326a1318f7cSSimon Glass False to just print a message. 3270d24de9dSSimon Glass cc_fname: Filename of Cc file for per-commit Cc 3280d24de9dSSimon Glass self_only: True to just email to yourself as a test 3296d819925SDoug Anderson in_reply_to: If set we'll pass this to git as --in-reply-to. 3306d819925SDoug Anderson Should be a message ID that this is in reply to. 3310d24de9dSSimon Glass 3320d24de9dSSimon Glass Returns: 3330d24de9dSSimon Glass Git command that was/would be run 3340d24de9dSSimon Glass 335a970048eSDoug Anderson # For the duration of this doctest pretend that we ran patman with ./patman 336a970048eSDoug Anderson >>> _old_argv0 = sys.argv[0] 337a970048eSDoug Anderson >>> sys.argv[0] = './patman' 338a970048eSDoug Anderson 3390d24de9dSSimon Glass >>> alias = {} 3400d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 3410d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 3420d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 3430d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 3440d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 3450d24de9dSSimon Glass >>> alias[os.getenv('USER')] = ['this-is-me@me.com'] 3460d24de9dSSimon Glass >>> series = series.Series() 3470d24de9dSSimon Glass >>> series.to = ['fred'] 3480d24de9dSSimon Glass >>> series.cc = ['mary'] 349a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 350a1318f7cSSimon Glass False, alias) 3510d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3520d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 353a1318f7cSSimon Glass >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \ 354a1318f7cSSimon Glass alias) 3550d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3560d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1' 3570d24de9dSSimon Glass >>> series.cc = ['all'] 358a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 359a1318f7cSSimon Glass True, alias) 3600d24de9dSSimon Glass 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \ 3610d24de9dSSimon Glass--cc-cmd cc-fname" cover p1 p2' 362a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 363a1318f7cSSimon Glass False, alias) 3640d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3650d24de9dSSimon Glass"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ 3660d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 367a970048eSDoug Anderson 368a970048eSDoug Anderson # Restore argv[0] since we clobbered it. 369a970048eSDoug Anderson >>> sys.argv[0] = _old_argv0 3700d24de9dSSimon Glass """ 371a1318f7cSSimon Glass to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error) 3720d24de9dSSimon Glass if not to: 373ee860c60SMasahiro Yamada git_config_to = command.Output('git', 'config', 'sendemail.to') 374ee860c60SMasahiro Yamada if not git_config_to: 375ee860c60SMasahiro Yamada print ("No recipient.\n" 376ee860c60SMasahiro Yamada "Please add something like this to a commit\n" 377ee860c60SMasahiro Yamada "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n" 378ee860c60SMasahiro Yamada "Or do something like this\n" 379ee860c60SMasahiro Yamada "git config sendemail.to u-boot@lists.denx.de") 3800d24de9dSSimon Glass return 381a1318f7cSSimon Glass cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error) 3820d24de9dSSimon Glass if self_only: 383a1318f7cSSimon Glass to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error) 3840d24de9dSSimon Glass cc = [] 3850d24de9dSSimon Glass cmd = ['git', 'send-email', '--annotate'] 3866d819925SDoug Anderson if in_reply_to: 3876d819925SDoug Anderson cmd.append('--in-reply-to="%s"' % in_reply_to) 3886d819925SDoug Anderson 3890d24de9dSSimon Glass cmd += to 3900d24de9dSSimon Glass cmd += cc 3910d24de9dSSimon Glass cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)] 3920d24de9dSSimon Glass if cover_fname: 3930d24de9dSSimon Glass cmd.append(cover_fname) 3940d24de9dSSimon Glass cmd += args 3950d24de9dSSimon Glass str = ' '.join(cmd) 3960d24de9dSSimon Glass if not dry_run: 3970d24de9dSSimon Glass os.system(str) 3980d24de9dSSimon Glass return str 3990d24de9dSSimon Glass 4000d24de9dSSimon Glass 401a1318f7cSSimon Glassdef LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): 4020d24de9dSSimon Glass """If an email address is an alias, look it up and return the full name 4030d24de9dSSimon Glass 4040d24de9dSSimon Glass TODO: Why not just use git's own alias feature? 4050d24de9dSSimon Glass 4060d24de9dSSimon Glass Args: 4070d24de9dSSimon Glass lookup_name: Alias or email address to look up 408a1318f7cSSimon Glass alias: Dictionary containing aliases (None to use settings default) 409a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 410a1318f7cSSimon Glass False to just print a message. 4110d24de9dSSimon Glass 4120d24de9dSSimon Glass Returns: 4130d24de9dSSimon Glass tuple: 4140d24de9dSSimon Glass list containing a list of email addresses 4150d24de9dSSimon Glass 4160d24de9dSSimon Glass Raises: 4170d24de9dSSimon Glass OSError if a recursive alias reference was found 4180d24de9dSSimon Glass ValueError if an alias was not found 4190d24de9dSSimon Glass 4200d24de9dSSimon Glass >>> alias = {} 4210d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 4220d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 4230d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 4240d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz'] 4250d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 4260d24de9dSSimon Glass >>> alias['loop'] = ['other', 'john', ' mary '] 4270d24de9dSSimon Glass >>> alias['other'] = ['loop', 'john', ' mary '] 4280d24de9dSSimon Glass >>> LookupEmail('mary', alias) 4290d24de9dSSimon Glass ['m.poppins@cloud.net'] 4300d24de9dSSimon Glass >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias) 4310d24de9dSSimon Glass ['arthur.wellesley@howe.ro.uk'] 4320d24de9dSSimon Glass >>> LookupEmail('boys', alias) 4330d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz'] 4340d24de9dSSimon Glass >>> LookupEmail('all', alias) 4350d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] 4360d24de9dSSimon Glass >>> LookupEmail('odd', alias) 4370d24de9dSSimon Glass Traceback (most recent call last): 4380d24de9dSSimon Glass ... 4390d24de9dSSimon Glass ValueError: Alias 'odd' not found 4400d24de9dSSimon Glass >>> LookupEmail('loop', alias) 4410d24de9dSSimon Glass Traceback (most recent call last): 4420d24de9dSSimon Glass ... 4430d24de9dSSimon Glass OSError: Recursive email alias at 'other' 444a1318f7cSSimon Glass >>> LookupEmail('odd', alias, raise_on_error=False) 445e752edcbSSimon Glass Alias 'odd' not found 446a1318f7cSSimon Glass [] 447a1318f7cSSimon Glass >>> # In this case the loop part will effectively be ignored. 448a1318f7cSSimon Glass >>> LookupEmail('loop', alias, raise_on_error=False) 449e752edcbSSimon Glass Recursive email alias at 'other' 450e752edcbSSimon Glass Recursive email alias at 'john' 451e752edcbSSimon Glass Recursive email alias at 'mary' 452a1318f7cSSimon Glass ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] 4530d24de9dSSimon Glass """ 4540d24de9dSSimon Glass if not alias: 4550d24de9dSSimon Glass alias = settings.alias 4560d24de9dSSimon Glass lookup_name = lookup_name.strip() 4570d24de9dSSimon Glass if '@' in lookup_name: # Perhaps a real email address 4580d24de9dSSimon Glass return [lookup_name] 4590d24de9dSSimon Glass 4600d24de9dSSimon Glass lookup_name = lookup_name.lower() 461a1318f7cSSimon Glass col = terminal.Color() 4620d24de9dSSimon Glass 4630d24de9dSSimon Glass out_list = [] 464a1318f7cSSimon Glass if level > 10: 465a1318f7cSSimon Glass msg = "Recursive email alias at '%s'" % lookup_name 466a1318f7cSSimon Glass if raise_on_error: 467a1318f7cSSimon Glass raise OSError, msg 468a1318f7cSSimon Glass else: 469a1318f7cSSimon Glass print col.Color(col.RED, msg) 470a1318f7cSSimon Glass return out_list 471a1318f7cSSimon Glass 4720d24de9dSSimon Glass if lookup_name: 4730d24de9dSSimon Glass if not lookup_name in alias: 474a1318f7cSSimon Glass msg = "Alias '%s' not found" % lookup_name 475a1318f7cSSimon Glass if raise_on_error: 476a1318f7cSSimon Glass raise ValueError, msg 477a1318f7cSSimon Glass else: 478a1318f7cSSimon Glass print col.Color(col.RED, msg) 479a1318f7cSSimon Glass return out_list 4800d24de9dSSimon Glass for item in alias[lookup_name]: 481a1318f7cSSimon Glass todo = LookupEmail(item, alias, raise_on_error, level + 1) 4820d24de9dSSimon Glass for new_item in todo: 4830d24de9dSSimon Glass if not new_item in out_list: 4840d24de9dSSimon Glass out_list.append(new_item) 4850d24de9dSSimon Glass 4860d24de9dSSimon Glass #print "No match for alias '%s'" % lookup_name 4870d24de9dSSimon Glass return out_list 4880d24de9dSSimon Glass 4890d24de9dSSimon Glassdef GetTopLevel(): 4900d24de9dSSimon Glass """Return name of top-level directory for this git repo. 4910d24de9dSSimon Glass 4920d24de9dSSimon Glass Returns: 4930d24de9dSSimon Glass Full path to git top-level directory 4940d24de9dSSimon Glass 4950d24de9dSSimon Glass This test makes sure that we are running tests in the right subdir 4960d24de9dSSimon Glass 497a970048eSDoug Anderson >>> os.path.realpath(os.path.dirname(__file__)) == \ 498a970048eSDoug Anderson os.path.join(GetTopLevel(), 'tools', 'patman') 4990d24de9dSSimon Glass True 5000d24de9dSSimon Glass """ 5010d24de9dSSimon Glass return command.OutputOneLine('git', 'rev-parse', '--show-toplevel') 5020d24de9dSSimon Glass 5030d24de9dSSimon Glassdef GetAliasFile(): 5040d24de9dSSimon Glass """Gets the name of the git alias file. 5050d24de9dSSimon Glass 5060d24de9dSSimon Glass Returns: 5070d24de9dSSimon Glass Filename of git alias file, or None if none 5080d24de9dSSimon Glass """ 509dc191505SSimon Glass fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile', 510dc191505SSimon Glass raise_on_error=False) 5110d24de9dSSimon Glass if fname: 5120d24de9dSSimon Glass fname = os.path.join(GetTopLevel(), fname.strip()) 5130d24de9dSSimon Glass return fname 5140d24de9dSSimon Glass 51587d65558SVikram Narayanandef GetDefaultUserName(): 51687d65558SVikram Narayanan """Gets the user.name from .gitconfig file. 51787d65558SVikram Narayanan 51887d65558SVikram Narayanan Returns: 51987d65558SVikram Narayanan User name found in .gitconfig file, or None if none 52087d65558SVikram Narayanan """ 52187d65558SVikram Narayanan uname = command.OutputOneLine('git', 'config', '--global', 'user.name') 52287d65558SVikram Narayanan return uname 52387d65558SVikram Narayanan 52487d65558SVikram Narayanandef GetDefaultUserEmail(): 52587d65558SVikram Narayanan """Gets the user.email from the global .gitconfig file. 52687d65558SVikram Narayanan 52787d65558SVikram Narayanan Returns: 52887d65558SVikram Narayanan User's email found in .gitconfig file, or None if none 52987d65558SVikram Narayanan """ 53087d65558SVikram Narayanan uemail = command.OutputOneLine('git', 'config', '--global', 'user.email') 53187d65558SVikram Narayanan return uemail 53287d65558SVikram Narayanan 5330d24de9dSSimon Glassdef Setup(): 5340d24de9dSSimon Glass """Set up git utils, by reading the alias files.""" 5350d24de9dSSimon Glass # Check for a git alias file also 5360b703dbcSSimon Glass global use_no_decorate 5370b703dbcSSimon Glass 5380d24de9dSSimon Glass alias_fname = GetAliasFile() 5390d24de9dSSimon Glass if alias_fname: 5400d24de9dSSimon Glass settings.ReadGitAliases(alias_fname) 541e49f14afSSimon Glass cmd = LogCmd(None, count=0) 542e49f14afSSimon Glass use_no_decorate = (command.RunPipe([cmd], raise_on_error=False) 543e49f14afSSimon Glass .return_code == 0) 5440d24de9dSSimon Glass 5455f6a1c42SSimon Glassdef GetHead(): 5465f6a1c42SSimon Glass """Get the hash of the current HEAD 5475f6a1c42SSimon Glass 5485f6a1c42SSimon Glass Returns: 5495f6a1c42SSimon Glass Hash of HEAD 5505f6a1c42SSimon Glass """ 5515f6a1c42SSimon Glass return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H') 5525f6a1c42SSimon Glass 5530d24de9dSSimon Glassif __name__ == "__main__": 5540d24de9dSSimon Glass import doctest 5550d24de9dSSimon Glass 5560d24de9dSSimon Glass doctest.testmod() 557