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] 36*9447a6b2SSimon 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 645f6a1c42SSimon Glassdef GetUpstream(git_dir, branch): 655f6a1c42SSimon Glass """Returns the name of the upstream for a branch 665f6a1c42SSimon Glass 675f6a1c42SSimon Glass Args: 685f6a1c42SSimon Glass git_dir: Git directory containing repo 695f6a1c42SSimon Glass branch: Name of branch 705f6a1c42SSimon Glass 715f6a1c42SSimon Glass Returns: 725f6a1c42SSimon Glass Name of upstream branch (e.g. 'upstream/master') or None if none 735f6a1c42SSimon Glass """ 74cce717a9SSimon Glass try: 755f6a1c42SSimon Glass remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 765f6a1c42SSimon Glass 'branch.%s.remote' % branch) 775f6a1c42SSimon Glass merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 785f6a1c42SSimon Glass 'branch.%s.merge' % branch) 79cce717a9SSimon Glass except: 80cce717a9SSimon Glass return None 81cce717a9SSimon Glass 825f6a1c42SSimon Glass if remote == '.': 835f6a1c42SSimon Glass return merge 845f6a1c42SSimon Glass elif remote and merge: 855f6a1c42SSimon Glass leaf = merge.split('/')[-1] 865f6a1c42SSimon Glass return '%s/%s' % (remote, leaf) 875f6a1c42SSimon Glass else: 885f6a1c42SSimon Glass raise ValueError, ("Cannot determine upstream branch for branch " 895f6a1c42SSimon Glass "'%s' remote='%s', merge='%s'" % (branch, remote, merge)) 905f6a1c42SSimon Glass 915f6a1c42SSimon Glass 925f6a1c42SSimon Glassdef GetRangeInBranch(git_dir, branch, include_upstream=False): 935f6a1c42SSimon Glass """Returns an expression for the commits in the given branch. 945f6a1c42SSimon Glass 955f6a1c42SSimon Glass Args: 965f6a1c42SSimon Glass git_dir: Directory containing git repo 975f6a1c42SSimon Glass branch: Name of branch 985f6a1c42SSimon Glass Return: 995f6a1c42SSimon Glass Expression in the form 'upstream..branch' which can be used to 100cce717a9SSimon Glass access the commits. If the branch does not exist, returns None. 1015f6a1c42SSimon Glass """ 1025f6a1c42SSimon Glass upstream = GetUpstream(git_dir, branch) 103cce717a9SSimon Glass if not upstream: 104cce717a9SSimon Glass return None 1055f6a1c42SSimon Glass return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) 1065f6a1c42SSimon Glass 1075f6a1c42SSimon Glassdef CountCommitsInBranch(git_dir, branch, include_upstream=False): 1085f6a1c42SSimon Glass """Returns the number of commits in the given branch. 1095f6a1c42SSimon Glass 1105f6a1c42SSimon Glass Args: 1115f6a1c42SSimon Glass git_dir: Directory containing git repo 1125f6a1c42SSimon Glass branch: Name of branch 1135f6a1c42SSimon Glass Return: 114cce717a9SSimon Glass Number of patches that exist on top of the branch, or None if the 115cce717a9SSimon Glass branch does not exist. 1165f6a1c42SSimon Glass """ 1175f6a1c42SSimon Glass range_expr = GetRangeInBranch(git_dir, branch, include_upstream) 118cce717a9SSimon Glass if not range_expr: 119cce717a9SSimon Glass return None 120cda2a611SSimon Glass pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True), 1215f6a1c42SSimon Glass ['wc', '-l']] 1225f6a1c42SSimon Glass result = command.RunPipe(pipe, capture=True, oneline=True) 1235f6a1c42SSimon Glass patch_count = int(result.stdout) 1245f6a1c42SSimon Glass return patch_count 1255f6a1c42SSimon Glass 1265f6a1c42SSimon Glassdef CountCommits(commit_range): 1275f6a1c42SSimon Glass """Returns the number of commits in the given range. 1285f6a1c42SSimon Glass 1295f6a1c42SSimon Glass Args: 1305f6a1c42SSimon Glass commit_range: Range of commits to count (e.g. 'HEAD..base') 1315f6a1c42SSimon Glass Return: 1325f6a1c42SSimon Glass Number of patches that exist on top of the branch 1335f6a1c42SSimon Glass """ 134cda2a611SSimon Glass pipe = [LogCmd(commit_range, oneline=True), 1355f6a1c42SSimon Glass ['wc', '-l']] 1365f6a1c42SSimon Glass stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout 1375f6a1c42SSimon Glass patch_count = int(stdout) 1385f6a1c42SSimon Glass return patch_count 1395f6a1c42SSimon Glass 1405f6a1c42SSimon Glassdef Checkout(commit_hash, git_dir=None, work_tree=None, force=False): 1415f6a1c42SSimon Glass """Checkout the selected commit for this build 1425f6a1c42SSimon Glass 1435f6a1c42SSimon Glass Args: 1445f6a1c42SSimon Glass commit_hash: Commit hash to check out 1455f6a1c42SSimon Glass """ 1465f6a1c42SSimon Glass pipe = ['git'] 1475f6a1c42SSimon Glass if git_dir: 1485f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 1495f6a1c42SSimon Glass if work_tree: 1505f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 1515f6a1c42SSimon Glass pipe.append('checkout') 1525f6a1c42SSimon Glass if force: 1535f6a1c42SSimon Glass pipe.append('-f') 1545f6a1c42SSimon Glass pipe.append(commit_hash) 1555f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True, raise_on_error=False) 1565f6a1c42SSimon Glass if result.return_code != 0: 1575f6a1c42SSimon Glass raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr) 1585f6a1c42SSimon Glass 1595f6a1c42SSimon Glassdef Clone(git_dir, output_dir): 1605f6a1c42SSimon Glass """Checkout the selected commit for this build 1615f6a1c42SSimon Glass 1625f6a1c42SSimon Glass Args: 1635f6a1c42SSimon Glass commit_hash: Commit hash to check out 1645f6a1c42SSimon Glass """ 1655f6a1c42SSimon Glass pipe = ['git', 'clone', git_dir, '.'] 1665f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True, cwd=output_dir) 1675f6a1c42SSimon Glass if result.return_code != 0: 1685f6a1c42SSimon Glass raise OSError, 'git clone: %s' % result.stderr 1695f6a1c42SSimon Glass 1705f6a1c42SSimon Glassdef Fetch(git_dir=None, work_tree=None): 1715f6a1c42SSimon Glass """Fetch from the origin repo 1725f6a1c42SSimon Glass 1735f6a1c42SSimon Glass Args: 1745f6a1c42SSimon Glass commit_hash: Commit hash to check out 1755f6a1c42SSimon Glass """ 1765f6a1c42SSimon Glass pipe = ['git'] 1775f6a1c42SSimon Glass if git_dir: 1785f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 1795f6a1c42SSimon Glass if work_tree: 1805f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 1815f6a1c42SSimon Glass pipe.append('fetch') 1825f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True) 1835f6a1c42SSimon Glass if result.return_code != 0: 1845f6a1c42SSimon Glass raise OSError, 'git fetch: %s' % result.stderr 1855f6a1c42SSimon Glass 1860d24de9dSSimon Glassdef CreatePatches(start, count, series): 1870d24de9dSSimon Glass """Create a series of patches from the top of the current branch. 1880d24de9dSSimon Glass 1890d24de9dSSimon Glass The patch files are written to the current directory using 1900d24de9dSSimon Glass git format-patch. 1910d24de9dSSimon Glass 1920d24de9dSSimon Glass Args: 1930d24de9dSSimon Glass start: Commit to start from: 0=HEAD, 1=next one, etc. 1940d24de9dSSimon Glass count: number of commits to include 1950d24de9dSSimon Glass Return: 1960d24de9dSSimon Glass Filename of cover letter 1970d24de9dSSimon Glass List of filenames of patch files 1980d24de9dSSimon Glass """ 1990d24de9dSSimon Glass if series.get('version'): 2000d24de9dSSimon Glass version = '%s ' % series['version'] 2010d24de9dSSimon Glass cmd = ['git', 'format-patch', '-M', '--signoff'] 2020d24de9dSSimon Glass if series.get('cover'): 2030d24de9dSSimon Glass cmd.append('--cover-letter') 2040d24de9dSSimon Glass prefix = series.GetPatchPrefix() 2050d24de9dSSimon Glass if prefix: 2060d24de9dSSimon Glass cmd += ['--subject-prefix=%s' % prefix] 2070d24de9dSSimon Glass cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)] 2080d24de9dSSimon Glass 2090d24de9dSSimon Glass stdout = command.RunList(cmd) 2100d24de9dSSimon Glass files = stdout.splitlines() 2110d24de9dSSimon Glass 2120d24de9dSSimon Glass # We have an extra file if there is a cover letter 2130d24de9dSSimon Glass if series.get('cover'): 2140d24de9dSSimon Glass return files[0], files[1:] 2150d24de9dSSimon Glass else: 2160d24de9dSSimon Glass return None, files 2170d24de9dSSimon Glass 218a1318f7cSSimon Glassdef BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): 2190d24de9dSSimon Glass """Build a list of email addresses based on an input list. 2200d24de9dSSimon Glass 2210d24de9dSSimon Glass Takes a list of email addresses and aliases, and turns this into a list 2220d24de9dSSimon Glass of only email address, by resolving any aliases that are present. 2230d24de9dSSimon Glass 2240d24de9dSSimon Glass If the tag is given, then each email address is prepended with this 2250d24de9dSSimon Glass tag and a space. If the tag starts with a minus sign (indicating a 2260d24de9dSSimon Glass command line parameter) then the email address is quoted. 2270d24de9dSSimon Glass 2280d24de9dSSimon Glass Args: 2290d24de9dSSimon Glass in_list: List of aliases/email addresses 2300d24de9dSSimon Glass tag: Text to put before each address 231a1318f7cSSimon Glass alias: Alias dictionary 232a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 233a1318f7cSSimon Glass False to just print a message. 2340d24de9dSSimon Glass 2350d24de9dSSimon Glass Returns: 2360d24de9dSSimon Glass List of email addresses 2370d24de9dSSimon Glass 2380d24de9dSSimon Glass >>> alias = {} 2390d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 2400d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 2410d24de9dSSimon Glass >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>'] 2420d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 2430d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 2440d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], None, alias) 2450d24de9dSSimon Glass ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>'] 2460d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], '--to', alias) 2470d24de9dSSimon Glass ['--to "j.bloggs@napier.co.nz"', \ 2480d24de9dSSimon Glass'--to "Mary Poppins <m.poppins@cloud.net>"'] 2490d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], 'Cc', alias) 2500d24de9dSSimon Glass ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>'] 2510d24de9dSSimon Glass """ 2520d24de9dSSimon Glass quote = '"' if tag and tag[0] == '-' else '' 2530d24de9dSSimon Glass raw = [] 2540d24de9dSSimon Glass for item in in_list: 255a1318f7cSSimon Glass raw += LookupEmail(item, alias, raise_on_error=raise_on_error) 2560d24de9dSSimon Glass result = [] 2570d24de9dSSimon Glass for item in raw: 2580d24de9dSSimon Glass if not item in result: 2590d24de9dSSimon Glass result.append(item) 2600d24de9dSSimon Glass if tag: 2610d24de9dSSimon Glass return ['%s %s%s%s' % (tag, quote, email, quote) for email in result] 2620d24de9dSSimon Glass return result 2630d24de9dSSimon Glass 264a1318f7cSSimon Glassdef EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, 2656d819925SDoug Anderson self_only=False, alias=None, in_reply_to=None): 2660d24de9dSSimon Glass """Email a patch series. 2670d24de9dSSimon Glass 2680d24de9dSSimon Glass Args: 2690d24de9dSSimon Glass series: Series object containing destination info 2700d24de9dSSimon Glass cover_fname: filename of cover letter 2710d24de9dSSimon Glass args: list of filenames of patch files 2720d24de9dSSimon Glass dry_run: Just return the command that would be run 273a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 274a1318f7cSSimon Glass False to just print a message. 2750d24de9dSSimon Glass cc_fname: Filename of Cc file for per-commit Cc 2760d24de9dSSimon Glass self_only: True to just email to yourself as a test 2776d819925SDoug Anderson in_reply_to: If set we'll pass this to git as --in-reply-to. 2786d819925SDoug Anderson Should be a message ID that this is in reply to. 2790d24de9dSSimon Glass 2800d24de9dSSimon Glass Returns: 2810d24de9dSSimon Glass Git command that was/would be run 2820d24de9dSSimon Glass 283a970048eSDoug Anderson # For the duration of this doctest pretend that we ran patman with ./patman 284a970048eSDoug Anderson >>> _old_argv0 = sys.argv[0] 285a970048eSDoug Anderson >>> sys.argv[0] = './patman' 286a970048eSDoug Anderson 2870d24de9dSSimon Glass >>> alias = {} 2880d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 2890d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 2900d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 2910d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 2920d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 2930d24de9dSSimon Glass >>> alias[os.getenv('USER')] = ['this-is-me@me.com'] 2940d24de9dSSimon Glass >>> series = series.Series() 2950d24de9dSSimon Glass >>> series.to = ['fred'] 2960d24de9dSSimon Glass >>> series.cc = ['mary'] 297a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 298a1318f7cSSimon Glass False, alias) 2990d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3000d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 301a1318f7cSSimon Glass >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \ 302a1318f7cSSimon Glass alias) 3030d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3040d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1' 3050d24de9dSSimon Glass >>> series.cc = ['all'] 306a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 307a1318f7cSSimon Glass True, alias) 3080d24de9dSSimon Glass 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \ 3090d24de9dSSimon Glass--cc-cmd cc-fname" cover p1 p2' 310a1318f7cSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ 311a1318f7cSSimon Glass False, alias) 3120d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3130d24de9dSSimon Glass"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ 3140d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 315a970048eSDoug Anderson 316a970048eSDoug Anderson # Restore argv[0] since we clobbered it. 317a970048eSDoug Anderson >>> sys.argv[0] = _old_argv0 3180d24de9dSSimon Glass """ 319a1318f7cSSimon Glass to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error) 3200d24de9dSSimon Glass if not to: 321ee860c60SMasahiro Yamada git_config_to = command.Output('git', 'config', 'sendemail.to') 322ee860c60SMasahiro Yamada if not git_config_to: 323ee860c60SMasahiro Yamada print ("No recipient.\n" 324ee860c60SMasahiro Yamada "Please add something like this to a commit\n" 325ee860c60SMasahiro Yamada "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n" 326ee860c60SMasahiro Yamada "Or do something like this\n" 327ee860c60SMasahiro Yamada "git config sendemail.to u-boot@lists.denx.de") 3280d24de9dSSimon Glass return 329a1318f7cSSimon Glass cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error) 3300d24de9dSSimon Glass if self_only: 331a1318f7cSSimon Glass to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error) 3320d24de9dSSimon Glass cc = [] 3330d24de9dSSimon Glass cmd = ['git', 'send-email', '--annotate'] 3346d819925SDoug Anderson if in_reply_to: 3356d819925SDoug Anderson cmd.append('--in-reply-to="%s"' % in_reply_to) 3366d819925SDoug Anderson 3370d24de9dSSimon Glass cmd += to 3380d24de9dSSimon Glass cmd += cc 3390d24de9dSSimon Glass cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)] 3400d24de9dSSimon Glass if cover_fname: 3410d24de9dSSimon Glass cmd.append(cover_fname) 3420d24de9dSSimon Glass cmd += args 3430d24de9dSSimon Glass str = ' '.join(cmd) 3440d24de9dSSimon Glass if not dry_run: 3450d24de9dSSimon Glass os.system(str) 3460d24de9dSSimon Glass return str 3470d24de9dSSimon Glass 3480d24de9dSSimon Glass 349a1318f7cSSimon Glassdef LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): 3500d24de9dSSimon Glass """If an email address is an alias, look it up and return the full name 3510d24de9dSSimon Glass 3520d24de9dSSimon Glass TODO: Why not just use git's own alias feature? 3530d24de9dSSimon Glass 3540d24de9dSSimon Glass Args: 3550d24de9dSSimon Glass lookup_name: Alias or email address to look up 356a1318f7cSSimon Glass alias: Dictionary containing aliases (None to use settings default) 357a1318f7cSSimon Glass raise_on_error: True to raise an error when an alias fails to match, 358a1318f7cSSimon Glass False to just print a message. 3590d24de9dSSimon Glass 3600d24de9dSSimon Glass Returns: 3610d24de9dSSimon Glass tuple: 3620d24de9dSSimon Glass list containing a list of email addresses 3630d24de9dSSimon Glass 3640d24de9dSSimon Glass Raises: 3650d24de9dSSimon Glass OSError if a recursive alias reference was found 3660d24de9dSSimon Glass ValueError if an alias was not found 3670d24de9dSSimon Glass 3680d24de9dSSimon Glass >>> alias = {} 3690d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 3700d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 3710d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 3720d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz'] 3730d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 3740d24de9dSSimon Glass >>> alias['loop'] = ['other', 'john', ' mary '] 3750d24de9dSSimon Glass >>> alias['other'] = ['loop', 'john', ' mary '] 3760d24de9dSSimon Glass >>> LookupEmail('mary', alias) 3770d24de9dSSimon Glass ['m.poppins@cloud.net'] 3780d24de9dSSimon Glass >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias) 3790d24de9dSSimon Glass ['arthur.wellesley@howe.ro.uk'] 3800d24de9dSSimon Glass >>> LookupEmail('boys', alias) 3810d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz'] 3820d24de9dSSimon Glass >>> LookupEmail('all', alias) 3830d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] 3840d24de9dSSimon Glass >>> LookupEmail('odd', alias) 3850d24de9dSSimon Glass Traceback (most recent call last): 3860d24de9dSSimon Glass ... 3870d24de9dSSimon Glass ValueError: Alias 'odd' not found 3880d24de9dSSimon Glass >>> LookupEmail('loop', alias) 3890d24de9dSSimon Glass Traceback (most recent call last): 3900d24de9dSSimon Glass ... 3910d24de9dSSimon Glass OSError: Recursive email alias at 'other' 392a1318f7cSSimon Glass >>> LookupEmail('odd', alias, raise_on_error=False) 393e752edcbSSimon Glass Alias 'odd' not found 394a1318f7cSSimon Glass [] 395a1318f7cSSimon Glass >>> # In this case the loop part will effectively be ignored. 396a1318f7cSSimon Glass >>> LookupEmail('loop', alias, raise_on_error=False) 397e752edcbSSimon Glass Recursive email alias at 'other' 398e752edcbSSimon Glass Recursive email alias at 'john' 399e752edcbSSimon Glass Recursive email alias at 'mary' 400a1318f7cSSimon Glass ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] 4010d24de9dSSimon Glass """ 4020d24de9dSSimon Glass if not alias: 4030d24de9dSSimon Glass alias = settings.alias 4040d24de9dSSimon Glass lookup_name = lookup_name.strip() 4050d24de9dSSimon Glass if '@' in lookup_name: # Perhaps a real email address 4060d24de9dSSimon Glass return [lookup_name] 4070d24de9dSSimon Glass 4080d24de9dSSimon Glass lookup_name = lookup_name.lower() 409a1318f7cSSimon Glass col = terminal.Color() 4100d24de9dSSimon Glass 4110d24de9dSSimon Glass out_list = [] 412a1318f7cSSimon Glass if level > 10: 413a1318f7cSSimon Glass msg = "Recursive email alias at '%s'" % lookup_name 414a1318f7cSSimon Glass if raise_on_error: 415a1318f7cSSimon Glass raise OSError, msg 416a1318f7cSSimon Glass else: 417a1318f7cSSimon Glass print col.Color(col.RED, msg) 418a1318f7cSSimon Glass return out_list 419a1318f7cSSimon Glass 4200d24de9dSSimon Glass if lookup_name: 4210d24de9dSSimon Glass if not lookup_name in alias: 422a1318f7cSSimon Glass msg = "Alias '%s' not found" % lookup_name 423a1318f7cSSimon Glass if raise_on_error: 424a1318f7cSSimon Glass raise ValueError, msg 425a1318f7cSSimon Glass else: 426a1318f7cSSimon Glass print col.Color(col.RED, msg) 427a1318f7cSSimon Glass return out_list 4280d24de9dSSimon Glass for item in alias[lookup_name]: 429a1318f7cSSimon Glass todo = LookupEmail(item, alias, raise_on_error, level + 1) 4300d24de9dSSimon Glass for new_item in todo: 4310d24de9dSSimon Glass if not new_item in out_list: 4320d24de9dSSimon Glass out_list.append(new_item) 4330d24de9dSSimon Glass 4340d24de9dSSimon Glass #print "No match for alias '%s'" % lookup_name 4350d24de9dSSimon Glass return out_list 4360d24de9dSSimon Glass 4370d24de9dSSimon Glassdef GetTopLevel(): 4380d24de9dSSimon Glass """Return name of top-level directory for this git repo. 4390d24de9dSSimon Glass 4400d24de9dSSimon Glass Returns: 4410d24de9dSSimon Glass Full path to git top-level directory 4420d24de9dSSimon Glass 4430d24de9dSSimon Glass This test makes sure that we are running tests in the right subdir 4440d24de9dSSimon Glass 445a970048eSDoug Anderson >>> os.path.realpath(os.path.dirname(__file__)) == \ 446a970048eSDoug Anderson os.path.join(GetTopLevel(), 'tools', 'patman') 4470d24de9dSSimon Glass True 4480d24de9dSSimon Glass """ 4490d24de9dSSimon Glass return command.OutputOneLine('git', 'rev-parse', '--show-toplevel') 4500d24de9dSSimon Glass 4510d24de9dSSimon Glassdef GetAliasFile(): 4520d24de9dSSimon Glass """Gets the name of the git alias file. 4530d24de9dSSimon Glass 4540d24de9dSSimon Glass Returns: 4550d24de9dSSimon Glass Filename of git alias file, or None if none 4560d24de9dSSimon Glass """ 457dc191505SSimon Glass fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile', 458dc191505SSimon Glass raise_on_error=False) 4590d24de9dSSimon Glass if fname: 4600d24de9dSSimon Glass fname = os.path.join(GetTopLevel(), fname.strip()) 4610d24de9dSSimon Glass return fname 4620d24de9dSSimon Glass 46387d65558SVikram Narayanandef GetDefaultUserName(): 46487d65558SVikram Narayanan """Gets the user.name from .gitconfig file. 46587d65558SVikram Narayanan 46687d65558SVikram Narayanan Returns: 46787d65558SVikram Narayanan User name found in .gitconfig file, or None if none 46887d65558SVikram Narayanan """ 46987d65558SVikram Narayanan uname = command.OutputOneLine('git', 'config', '--global', 'user.name') 47087d65558SVikram Narayanan return uname 47187d65558SVikram Narayanan 47287d65558SVikram Narayanandef GetDefaultUserEmail(): 47387d65558SVikram Narayanan """Gets the user.email from the global .gitconfig file. 47487d65558SVikram Narayanan 47587d65558SVikram Narayanan Returns: 47687d65558SVikram Narayanan User's email found in .gitconfig file, or None if none 47787d65558SVikram Narayanan """ 47887d65558SVikram Narayanan uemail = command.OutputOneLine('git', 'config', '--global', 'user.email') 47987d65558SVikram Narayanan return uemail 48087d65558SVikram Narayanan 4810d24de9dSSimon Glassdef Setup(): 4820d24de9dSSimon Glass """Set up git utils, by reading the alias files.""" 4830d24de9dSSimon Glass # Check for a git alias file also 4840d24de9dSSimon Glass alias_fname = GetAliasFile() 4850d24de9dSSimon Glass if alias_fname: 4860d24de9dSSimon Glass settings.ReadGitAliases(alias_fname) 487e49f14afSSimon Glass cmd = LogCmd(None, count=0) 488e49f14afSSimon Glass use_no_decorate = (command.RunPipe([cmd], raise_on_error=False) 489e49f14afSSimon Glass .return_code == 0) 4900d24de9dSSimon Glass 4915f6a1c42SSimon Glassdef GetHead(): 4925f6a1c42SSimon Glass """Get the hash of the current HEAD 4935f6a1c42SSimon Glass 4945f6a1c42SSimon Glass Returns: 4955f6a1c42SSimon Glass Hash of HEAD 4965f6a1c42SSimon Glass """ 4975f6a1c42SSimon Glass return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H') 4985f6a1c42SSimon Glass 4990d24de9dSSimon Glassif __name__ == "__main__": 5000d24de9dSSimon Glass import doctest 5010d24de9dSSimon Glass 5020d24de9dSSimon Glass doctest.testmod() 503