10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors. 20d24de9dSSimon Glass# 30d24de9dSSimon Glass# See file CREDITS for list of people who contributed to this 40d24de9dSSimon Glass# project. 50d24de9dSSimon Glass# 60d24de9dSSimon Glass# This program is free software; you can redistribute it and/or 70d24de9dSSimon Glass# modify it under the terms of the GNU General Public License as 80d24de9dSSimon Glass# published by the Free Software Foundation; either version 2 of 90d24de9dSSimon Glass# the License, or (at your option) any later version. 100d24de9dSSimon Glass# 110d24de9dSSimon Glass# This program is distributed in the hope that it will be useful, 120d24de9dSSimon Glass# but WITHOUT ANY WARRANTY; without even the implied warranty of 130d24de9dSSimon Glass# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 140d24de9dSSimon Glass# GNU General Public License for more details. 150d24de9dSSimon Glass# 160d24de9dSSimon Glass# You should have received a copy of the GNU General Public License 170d24de9dSSimon Glass# along with this program; if not, write to the Free Software 180d24de9dSSimon Glass# Foundation, Inc., 59 Temple Place, Suite 330, Boston, 190d24de9dSSimon Glass# MA 02111-1307 USA 200d24de9dSSimon Glass# 210d24de9dSSimon Glass 220d24de9dSSimon Glassimport command 230d24de9dSSimon Glassimport re 240d24de9dSSimon Glassimport os 250d24de9dSSimon Glassimport series 260d24de9dSSimon Glassimport subprocess 270d24de9dSSimon Glassimport sys 280d24de9dSSimon Glassimport terminal 290d24de9dSSimon Glass 30*5f6a1c42SSimon Glassimport settings 31*5f6a1c42SSimon Glass 320d24de9dSSimon Glass 330d24de9dSSimon Glassdef CountCommitsToBranch(): 340d24de9dSSimon Glass """Returns number of commits between HEAD and the tracking branch. 350d24de9dSSimon Glass 360d24de9dSSimon Glass This looks back to the tracking branch and works out the number of commits 370d24de9dSSimon Glass since then. 380d24de9dSSimon Glass 390d24de9dSSimon Glass Return: 400d24de9dSSimon Glass Number of patches that exist on top of the branch 410d24de9dSSimon Glass """ 427f14f30aSAlbert ARIBAUD pipe = [['git', 'log', '--no-color', '--oneline', '@{upstream}..'], 430d24de9dSSimon Glass ['wc', '-l']] 44a10fd93cSSimon Glass stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout 450d24de9dSSimon Glass patch_count = int(stdout) 460d24de9dSSimon Glass return patch_count 470d24de9dSSimon Glass 48*5f6a1c42SSimon Glassdef GetUpstream(git_dir, branch): 49*5f6a1c42SSimon Glass """Returns the name of the upstream for a branch 50*5f6a1c42SSimon Glass 51*5f6a1c42SSimon Glass Args: 52*5f6a1c42SSimon Glass git_dir: Git directory containing repo 53*5f6a1c42SSimon Glass branch: Name of branch 54*5f6a1c42SSimon Glass 55*5f6a1c42SSimon Glass Returns: 56*5f6a1c42SSimon Glass Name of upstream branch (e.g. 'upstream/master') or None if none 57*5f6a1c42SSimon Glass """ 58*5f6a1c42SSimon Glass remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 59*5f6a1c42SSimon Glass 'branch.%s.remote' % branch) 60*5f6a1c42SSimon Glass merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config', 61*5f6a1c42SSimon Glass 'branch.%s.merge' % branch) 62*5f6a1c42SSimon Glass if remote == '.': 63*5f6a1c42SSimon Glass return merge 64*5f6a1c42SSimon Glass elif remote and merge: 65*5f6a1c42SSimon Glass leaf = merge.split('/')[-1] 66*5f6a1c42SSimon Glass return '%s/%s' % (remote, leaf) 67*5f6a1c42SSimon Glass else: 68*5f6a1c42SSimon Glass raise ValueError, ("Cannot determine upstream branch for branch " 69*5f6a1c42SSimon Glass "'%s' remote='%s', merge='%s'" % (branch, remote, merge)) 70*5f6a1c42SSimon Glass 71*5f6a1c42SSimon Glass 72*5f6a1c42SSimon Glassdef GetRangeInBranch(git_dir, branch, include_upstream=False): 73*5f6a1c42SSimon Glass """Returns an expression for the commits in the given branch. 74*5f6a1c42SSimon Glass 75*5f6a1c42SSimon Glass Args: 76*5f6a1c42SSimon Glass git_dir: Directory containing git repo 77*5f6a1c42SSimon Glass branch: Name of branch 78*5f6a1c42SSimon Glass Return: 79*5f6a1c42SSimon Glass Expression in the form 'upstream..branch' which can be used to 80*5f6a1c42SSimon Glass access the commits. 81*5f6a1c42SSimon Glass """ 82*5f6a1c42SSimon Glass upstream = GetUpstream(git_dir, branch) 83*5f6a1c42SSimon Glass return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) 84*5f6a1c42SSimon Glass 85*5f6a1c42SSimon Glassdef CountCommitsInBranch(git_dir, branch, include_upstream=False): 86*5f6a1c42SSimon Glass """Returns the number of commits in the given branch. 87*5f6a1c42SSimon Glass 88*5f6a1c42SSimon Glass Args: 89*5f6a1c42SSimon Glass git_dir: Directory containing git repo 90*5f6a1c42SSimon Glass branch: Name of branch 91*5f6a1c42SSimon Glass Return: 92*5f6a1c42SSimon Glass Number of patches that exist on top of the branch 93*5f6a1c42SSimon Glass """ 94*5f6a1c42SSimon Glass range_expr = GetRangeInBranch(git_dir, branch, include_upstream) 95*5f6a1c42SSimon Glass pipe = [['git', '--git-dir', git_dir, 'log', '--oneline', range_expr], 96*5f6a1c42SSimon Glass ['wc', '-l']] 97*5f6a1c42SSimon Glass result = command.RunPipe(pipe, capture=True, oneline=True) 98*5f6a1c42SSimon Glass patch_count = int(result.stdout) 99*5f6a1c42SSimon Glass return patch_count 100*5f6a1c42SSimon Glass 101*5f6a1c42SSimon Glassdef CountCommits(commit_range): 102*5f6a1c42SSimon Glass """Returns the number of commits in the given range. 103*5f6a1c42SSimon Glass 104*5f6a1c42SSimon Glass Args: 105*5f6a1c42SSimon Glass commit_range: Range of commits to count (e.g. 'HEAD..base') 106*5f6a1c42SSimon Glass Return: 107*5f6a1c42SSimon Glass Number of patches that exist on top of the branch 108*5f6a1c42SSimon Glass """ 109*5f6a1c42SSimon Glass pipe = [['git', 'log', '--oneline', commit_range], 110*5f6a1c42SSimon Glass ['wc', '-l']] 111*5f6a1c42SSimon Glass stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout 112*5f6a1c42SSimon Glass patch_count = int(stdout) 113*5f6a1c42SSimon Glass return patch_count 114*5f6a1c42SSimon Glass 115*5f6a1c42SSimon Glassdef Checkout(commit_hash, git_dir=None, work_tree=None, force=False): 116*5f6a1c42SSimon Glass """Checkout the selected commit for this build 117*5f6a1c42SSimon Glass 118*5f6a1c42SSimon Glass Args: 119*5f6a1c42SSimon Glass commit_hash: Commit hash to check out 120*5f6a1c42SSimon Glass """ 121*5f6a1c42SSimon Glass pipe = ['git'] 122*5f6a1c42SSimon Glass if git_dir: 123*5f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 124*5f6a1c42SSimon Glass if work_tree: 125*5f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 126*5f6a1c42SSimon Glass pipe.append('checkout') 127*5f6a1c42SSimon Glass if force: 128*5f6a1c42SSimon Glass pipe.append('-f') 129*5f6a1c42SSimon Glass pipe.append(commit_hash) 130*5f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True, raise_on_error=False) 131*5f6a1c42SSimon Glass if result.return_code != 0: 132*5f6a1c42SSimon Glass raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr) 133*5f6a1c42SSimon Glass 134*5f6a1c42SSimon Glassdef Clone(git_dir, output_dir): 135*5f6a1c42SSimon Glass """Checkout the selected commit for this build 136*5f6a1c42SSimon Glass 137*5f6a1c42SSimon Glass Args: 138*5f6a1c42SSimon Glass commit_hash: Commit hash to check out 139*5f6a1c42SSimon Glass """ 140*5f6a1c42SSimon Glass pipe = ['git', 'clone', git_dir, '.'] 141*5f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True, cwd=output_dir) 142*5f6a1c42SSimon Glass if result.return_code != 0: 143*5f6a1c42SSimon Glass raise OSError, 'git clone: %s' % result.stderr 144*5f6a1c42SSimon Glass 145*5f6a1c42SSimon Glassdef Fetch(git_dir=None, work_tree=None): 146*5f6a1c42SSimon Glass """Fetch from the origin repo 147*5f6a1c42SSimon Glass 148*5f6a1c42SSimon Glass Args: 149*5f6a1c42SSimon Glass commit_hash: Commit hash to check out 150*5f6a1c42SSimon Glass """ 151*5f6a1c42SSimon Glass pipe = ['git'] 152*5f6a1c42SSimon Glass if git_dir: 153*5f6a1c42SSimon Glass pipe.extend(['--git-dir', git_dir]) 154*5f6a1c42SSimon Glass if work_tree: 155*5f6a1c42SSimon Glass pipe.extend(['--work-tree', work_tree]) 156*5f6a1c42SSimon Glass pipe.append('fetch') 157*5f6a1c42SSimon Glass result = command.RunPipe([pipe], capture=True) 158*5f6a1c42SSimon Glass if result.return_code != 0: 159*5f6a1c42SSimon Glass raise OSError, 'git fetch: %s' % result.stderr 160*5f6a1c42SSimon Glass 1610d24de9dSSimon Glassdef CreatePatches(start, count, series): 1620d24de9dSSimon Glass """Create a series of patches from the top of the current branch. 1630d24de9dSSimon Glass 1640d24de9dSSimon Glass The patch files are written to the current directory using 1650d24de9dSSimon Glass git format-patch. 1660d24de9dSSimon Glass 1670d24de9dSSimon Glass Args: 1680d24de9dSSimon Glass start: Commit to start from: 0=HEAD, 1=next one, etc. 1690d24de9dSSimon Glass count: number of commits to include 1700d24de9dSSimon Glass Return: 1710d24de9dSSimon Glass Filename of cover letter 1720d24de9dSSimon Glass List of filenames of patch files 1730d24de9dSSimon Glass """ 1740d24de9dSSimon Glass if series.get('version'): 1750d24de9dSSimon Glass version = '%s ' % series['version'] 1760d24de9dSSimon Glass cmd = ['git', 'format-patch', '-M', '--signoff'] 1770d24de9dSSimon Glass if series.get('cover'): 1780d24de9dSSimon Glass cmd.append('--cover-letter') 1790d24de9dSSimon Glass prefix = series.GetPatchPrefix() 1800d24de9dSSimon Glass if prefix: 1810d24de9dSSimon Glass cmd += ['--subject-prefix=%s' % prefix] 1820d24de9dSSimon Glass cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)] 1830d24de9dSSimon Glass 1840d24de9dSSimon Glass stdout = command.RunList(cmd) 1850d24de9dSSimon Glass files = stdout.splitlines() 1860d24de9dSSimon Glass 1870d24de9dSSimon Glass # We have an extra file if there is a cover letter 1880d24de9dSSimon Glass if series.get('cover'): 1890d24de9dSSimon Glass return files[0], files[1:] 1900d24de9dSSimon Glass else: 1910d24de9dSSimon Glass return None, files 1920d24de9dSSimon Glass 1930d24de9dSSimon Glassdef ApplyPatch(verbose, fname): 1940d24de9dSSimon Glass """Apply a patch with git am to test it 1950d24de9dSSimon Glass 1960d24de9dSSimon Glass TODO: Convert these to use command, with stderr option 1970d24de9dSSimon Glass 1980d24de9dSSimon Glass Args: 1990d24de9dSSimon Glass fname: filename of patch file to apply 2000d24de9dSSimon Glass """ 2010d24de9dSSimon Glass cmd = ['git', 'am', fname] 2020d24de9dSSimon Glass pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, 2030d24de9dSSimon Glass stderr=subprocess.PIPE) 2040d24de9dSSimon Glass stdout, stderr = pipe.communicate() 2050d24de9dSSimon Glass re_error = re.compile('^error: patch failed: (.+):(\d+)') 2060d24de9dSSimon Glass for line in stderr.splitlines(): 2070d24de9dSSimon Glass if verbose: 2080d24de9dSSimon Glass print line 2090d24de9dSSimon Glass match = re_error.match(line) 2100d24de9dSSimon Glass if match: 2110d24de9dSSimon Glass print GetWarningMsg('warning', match.group(1), int(match.group(2)), 2120d24de9dSSimon Glass 'Patch failed') 2130d24de9dSSimon Glass return pipe.returncode == 0, stdout 2140d24de9dSSimon Glass 2150d24de9dSSimon Glassdef ApplyPatches(verbose, args, start_point): 2160d24de9dSSimon Glass """Apply the patches with git am to make sure all is well 2170d24de9dSSimon Glass 2180d24de9dSSimon Glass Args: 2190d24de9dSSimon Glass verbose: Print out 'git am' output verbatim 2200d24de9dSSimon Glass args: List of patch files to apply 2210d24de9dSSimon Glass start_point: Number of commits back from HEAD to start applying. 2220d24de9dSSimon Glass Normally this is len(args), but it can be larger if a start 2230d24de9dSSimon Glass offset was given. 2240d24de9dSSimon Glass """ 2250d24de9dSSimon Glass error_count = 0 2260d24de9dSSimon Glass col = terminal.Color() 2270d24de9dSSimon Glass 2280d24de9dSSimon Glass # Figure out our current position 2290d24de9dSSimon Glass cmd = ['git', 'name-rev', 'HEAD', '--name-only'] 2300d24de9dSSimon Glass pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) 2310d24de9dSSimon Glass stdout, stderr = pipe.communicate() 2320d24de9dSSimon Glass if pipe.returncode: 2330d24de9dSSimon Glass str = 'Could not find current commit name' 2340d24de9dSSimon Glass print col.Color(col.RED, str) 2350d24de9dSSimon Glass print stdout 2360d24de9dSSimon Glass return False 2370d24de9dSSimon Glass old_head = stdout.splitlines()[0] 2380d24de9dSSimon Glass 2390d24de9dSSimon Glass # Checkout the required start point 2400d24de9dSSimon Glass cmd = ['git', 'checkout', 'HEAD~%d' % start_point] 2410d24de9dSSimon Glass pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, 2420d24de9dSSimon Glass stderr=subprocess.PIPE) 2430d24de9dSSimon Glass stdout, stderr = pipe.communicate() 2440d24de9dSSimon Glass if pipe.returncode: 2450d24de9dSSimon Glass str = 'Could not move to commit before patch series' 2460d24de9dSSimon Glass print col.Color(col.RED, str) 2470d24de9dSSimon Glass print stdout, stderr 2480d24de9dSSimon Glass return False 2490d24de9dSSimon Glass 2500d24de9dSSimon Glass # Apply all the patches 2510d24de9dSSimon Glass for fname in args: 2520d24de9dSSimon Glass ok, stdout = ApplyPatch(verbose, fname) 2530d24de9dSSimon Glass if not ok: 2540d24de9dSSimon Glass print col.Color(col.RED, 'git am returned errors for %s: will ' 2550d24de9dSSimon Glass 'skip this patch' % fname) 2560d24de9dSSimon Glass if verbose: 2570d24de9dSSimon Glass print stdout 2580d24de9dSSimon Glass error_count += 1 2590d24de9dSSimon Glass cmd = ['git', 'am', '--skip'] 2600d24de9dSSimon Glass pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) 2610d24de9dSSimon Glass stdout, stderr = pipe.communicate() 2620d24de9dSSimon Glass if pipe.returncode != 0: 2630d24de9dSSimon Glass print col.Color(col.RED, 'Unable to skip patch! Aborting...') 2640d24de9dSSimon Glass print stdout 2650d24de9dSSimon Glass break 2660d24de9dSSimon Glass 2670d24de9dSSimon Glass # Return to our previous position 2680d24de9dSSimon Glass cmd = ['git', 'checkout', old_head] 2690d24de9dSSimon Glass pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 2700d24de9dSSimon Glass stdout, stderr = pipe.communicate() 2710d24de9dSSimon Glass if pipe.returncode: 2720d24de9dSSimon Glass print col.Color(col.RED, 'Could not move back to head commit') 2730d24de9dSSimon Glass print stdout, stderr 2740d24de9dSSimon Glass return error_count == 0 2750d24de9dSSimon Glass 2760d24de9dSSimon Glassdef BuildEmailList(in_list, tag=None, alias=None): 2770d24de9dSSimon Glass """Build a list of email addresses based on an input list. 2780d24de9dSSimon Glass 2790d24de9dSSimon Glass Takes a list of email addresses and aliases, and turns this into a list 2800d24de9dSSimon Glass of only email address, by resolving any aliases that are present. 2810d24de9dSSimon Glass 2820d24de9dSSimon Glass If the tag is given, then each email address is prepended with this 2830d24de9dSSimon Glass tag and a space. If the tag starts with a minus sign (indicating a 2840d24de9dSSimon Glass command line parameter) then the email address is quoted. 2850d24de9dSSimon Glass 2860d24de9dSSimon Glass Args: 2870d24de9dSSimon Glass in_list: List of aliases/email addresses 2880d24de9dSSimon Glass tag: Text to put before each address 2890d24de9dSSimon Glass 2900d24de9dSSimon Glass Returns: 2910d24de9dSSimon Glass List of email addresses 2920d24de9dSSimon Glass 2930d24de9dSSimon Glass >>> alias = {} 2940d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 2950d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 2960d24de9dSSimon Glass >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>'] 2970d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 2980d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 2990d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], None, alias) 3000d24de9dSSimon Glass ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>'] 3010d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], '--to', alias) 3020d24de9dSSimon Glass ['--to "j.bloggs@napier.co.nz"', \ 3030d24de9dSSimon Glass'--to "Mary Poppins <m.poppins@cloud.net>"'] 3040d24de9dSSimon Glass >>> BuildEmailList(['john', 'mary'], 'Cc', alias) 3050d24de9dSSimon Glass ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>'] 3060d24de9dSSimon Glass """ 3070d24de9dSSimon Glass quote = '"' if tag and tag[0] == '-' else '' 3080d24de9dSSimon Glass raw = [] 3090d24de9dSSimon Glass for item in in_list: 3100d24de9dSSimon Glass raw += LookupEmail(item, alias) 3110d24de9dSSimon Glass result = [] 3120d24de9dSSimon Glass for item in raw: 3130d24de9dSSimon Glass if not item in result: 3140d24de9dSSimon Glass result.append(item) 3150d24de9dSSimon Glass if tag: 3160d24de9dSSimon Glass return ['%s %s%s%s' % (tag, quote, email, quote) for email in result] 3170d24de9dSSimon Glass return result 3180d24de9dSSimon Glass 3190d24de9dSSimon Glassdef EmailPatches(series, cover_fname, args, dry_run, cc_fname, 3200d24de9dSSimon Glass self_only=False, alias=None): 3210d24de9dSSimon Glass """Email a patch series. 3220d24de9dSSimon Glass 3230d24de9dSSimon Glass Args: 3240d24de9dSSimon Glass series: Series object containing destination info 3250d24de9dSSimon Glass cover_fname: filename of cover letter 3260d24de9dSSimon Glass args: list of filenames of patch files 3270d24de9dSSimon Glass dry_run: Just return the command that would be run 3280d24de9dSSimon Glass cc_fname: Filename of Cc file for per-commit Cc 3290d24de9dSSimon Glass self_only: True to just email to yourself as a test 3300d24de9dSSimon Glass 3310d24de9dSSimon Glass Returns: 3320d24de9dSSimon Glass Git command that was/would be run 3330d24de9dSSimon Glass 334a970048eSDoug Anderson # For the duration of this doctest pretend that we ran patman with ./patman 335a970048eSDoug Anderson >>> _old_argv0 = sys.argv[0] 336a970048eSDoug Anderson >>> sys.argv[0] = './patman' 337a970048eSDoug Anderson 3380d24de9dSSimon Glass >>> alias = {} 3390d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 3400d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 3410d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 3420d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john'] 3430d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 3440d24de9dSSimon Glass >>> alias[os.getenv('USER')] = ['this-is-me@me.com'] 3450d24de9dSSimon Glass >>> series = series.Series() 3460d24de9dSSimon Glass >>> series.to = ['fred'] 3470d24de9dSSimon Glass >>> series.cc = ['mary'] 3480d24de9dSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, 'cc-fname', False, \ 3490d24de9dSSimon Glass alias) 3500d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3510d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 3520d24de9dSSimon Glass >>> EmailPatches(series, None, ['p1'], True, 'cc-fname', False, alias) 3530d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3540d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1' 3550d24de9dSSimon Glass >>> series.cc = ['all'] 3560d24de9dSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, 'cc-fname', True, \ 3570d24de9dSSimon Glass alias) 3580d24de9dSSimon Glass 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \ 3590d24de9dSSimon Glass--cc-cmd cc-fname" cover p1 p2' 3600d24de9dSSimon Glass >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, 'cc-fname', False, \ 3610d24de9dSSimon Glass alias) 3620d24de9dSSimon Glass 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 3630d24de9dSSimon Glass"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ 3640d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' 365a970048eSDoug Anderson 366a970048eSDoug Anderson # Restore argv[0] since we clobbered it. 367a970048eSDoug Anderson >>> sys.argv[0] = _old_argv0 3680d24de9dSSimon Glass """ 3690d24de9dSSimon Glass to = BuildEmailList(series.get('to'), '--to', alias) 3700d24de9dSSimon Glass if not to: 3710d24de9dSSimon Glass print ("No recipient, please add something like this to a commit\n" 3720d24de9dSSimon Glass "Series-to: Fred Bloggs <f.blogs@napier.co.nz>") 3730d24de9dSSimon Glass return 3740d24de9dSSimon Glass cc = BuildEmailList(series.get('cc'), '--cc', alias) 3750d24de9dSSimon Glass if self_only: 3760d24de9dSSimon Glass to = BuildEmailList([os.getenv('USER')], '--to', alias) 3770d24de9dSSimon Glass cc = [] 3780d24de9dSSimon Glass cmd = ['git', 'send-email', '--annotate'] 3790d24de9dSSimon Glass cmd += to 3800d24de9dSSimon Glass cmd += cc 3810d24de9dSSimon Glass cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)] 3820d24de9dSSimon Glass if cover_fname: 3830d24de9dSSimon Glass cmd.append(cover_fname) 3840d24de9dSSimon Glass cmd += args 3850d24de9dSSimon Glass str = ' '.join(cmd) 3860d24de9dSSimon Glass if not dry_run: 3870d24de9dSSimon Glass os.system(str) 3880d24de9dSSimon Glass return str 3890d24de9dSSimon Glass 3900d24de9dSSimon Glass 3910d24de9dSSimon Glassdef LookupEmail(lookup_name, alias=None, level=0): 3920d24de9dSSimon Glass """If an email address is an alias, look it up and return the full name 3930d24de9dSSimon Glass 3940d24de9dSSimon Glass TODO: Why not just use git's own alias feature? 3950d24de9dSSimon Glass 3960d24de9dSSimon Glass Args: 3970d24de9dSSimon Glass lookup_name: Alias or email address to look up 3980d24de9dSSimon Glass 3990d24de9dSSimon Glass Returns: 4000d24de9dSSimon Glass tuple: 4010d24de9dSSimon Glass list containing a list of email addresses 4020d24de9dSSimon Glass 4030d24de9dSSimon Glass Raises: 4040d24de9dSSimon Glass OSError if a recursive alias reference was found 4050d24de9dSSimon Glass ValueError if an alias was not found 4060d24de9dSSimon Glass 4070d24de9dSSimon Glass >>> alias = {} 4080d24de9dSSimon Glass >>> alias['fred'] = ['f.bloggs@napier.co.nz'] 4090d24de9dSSimon Glass >>> alias['john'] = ['j.bloggs@napier.co.nz'] 4100d24de9dSSimon Glass >>> alias['mary'] = ['m.poppins@cloud.net'] 4110d24de9dSSimon Glass >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz'] 4120d24de9dSSimon Glass >>> alias['all'] = ['fred ', 'john', ' mary '] 4130d24de9dSSimon Glass >>> alias['loop'] = ['other', 'john', ' mary '] 4140d24de9dSSimon Glass >>> alias['other'] = ['loop', 'john', ' mary '] 4150d24de9dSSimon Glass >>> LookupEmail('mary', alias) 4160d24de9dSSimon Glass ['m.poppins@cloud.net'] 4170d24de9dSSimon Glass >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias) 4180d24de9dSSimon Glass ['arthur.wellesley@howe.ro.uk'] 4190d24de9dSSimon Glass >>> LookupEmail('boys', alias) 4200d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz'] 4210d24de9dSSimon Glass >>> LookupEmail('all', alias) 4220d24de9dSSimon Glass ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] 4230d24de9dSSimon Glass >>> LookupEmail('odd', alias) 4240d24de9dSSimon Glass Traceback (most recent call last): 4250d24de9dSSimon Glass ... 4260d24de9dSSimon Glass ValueError: Alias 'odd' not found 4270d24de9dSSimon Glass >>> LookupEmail('loop', alias) 4280d24de9dSSimon Glass Traceback (most recent call last): 4290d24de9dSSimon Glass ... 4300d24de9dSSimon Glass OSError: Recursive email alias at 'other' 4310d24de9dSSimon Glass """ 4320d24de9dSSimon Glass if not alias: 4330d24de9dSSimon Glass alias = settings.alias 4340d24de9dSSimon Glass lookup_name = lookup_name.strip() 4350d24de9dSSimon Glass if '@' in lookup_name: # Perhaps a real email address 4360d24de9dSSimon Glass return [lookup_name] 4370d24de9dSSimon Glass 4380d24de9dSSimon Glass lookup_name = lookup_name.lower() 4390d24de9dSSimon Glass 4400d24de9dSSimon Glass if level > 10: 4410d24de9dSSimon Glass raise OSError, "Recursive email alias at '%s'" % lookup_name 4420d24de9dSSimon Glass 4430d24de9dSSimon Glass out_list = [] 4440d24de9dSSimon Glass if lookup_name: 4450d24de9dSSimon Glass if not lookup_name in alias: 4460d24de9dSSimon Glass raise ValueError, "Alias '%s' not found" % lookup_name 4470d24de9dSSimon Glass for item in alias[lookup_name]: 4480d24de9dSSimon Glass todo = LookupEmail(item, alias, level + 1) 4490d24de9dSSimon Glass for new_item in todo: 4500d24de9dSSimon Glass if not new_item in out_list: 4510d24de9dSSimon Glass out_list.append(new_item) 4520d24de9dSSimon Glass 4530d24de9dSSimon Glass #print "No match for alias '%s'" % lookup_name 4540d24de9dSSimon Glass return out_list 4550d24de9dSSimon Glass 4560d24de9dSSimon Glassdef GetTopLevel(): 4570d24de9dSSimon Glass """Return name of top-level directory for this git repo. 4580d24de9dSSimon Glass 4590d24de9dSSimon Glass Returns: 4600d24de9dSSimon Glass Full path to git top-level directory 4610d24de9dSSimon Glass 4620d24de9dSSimon Glass This test makes sure that we are running tests in the right subdir 4630d24de9dSSimon Glass 464a970048eSDoug Anderson >>> os.path.realpath(os.path.dirname(__file__)) == \ 465a970048eSDoug Anderson os.path.join(GetTopLevel(), 'tools', 'patman') 4660d24de9dSSimon Glass True 4670d24de9dSSimon Glass """ 4680d24de9dSSimon Glass return command.OutputOneLine('git', 'rev-parse', '--show-toplevel') 4690d24de9dSSimon Glass 4700d24de9dSSimon Glassdef GetAliasFile(): 4710d24de9dSSimon Glass """Gets the name of the git alias file. 4720d24de9dSSimon Glass 4730d24de9dSSimon Glass Returns: 4740d24de9dSSimon Glass Filename of git alias file, or None if none 4750d24de9dSSimon Glass """ 476dc191505SSimon Glass fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile', 477dc191505SSimon Glass raise_on_error=False) 4780d24de9dSSimon Glass if fname: 4790d24de9dSSimon Glass fname = os.path.join(GetTopLevel(), fname.strip()) 4800d24de9dSSimon Glass return fname 4810d24de9dSSimon Glass 48287d65558SVikram Narayanandef GetDefaultUserName(): 48387d65558SVikram Narayanan """Gets the user.name from .gitconfig file. 48487d65558SVikram Narayanan 48587d65558SVikram Narayanan Returns: 48687d65558SVikram Narayanan User name found in .gitconfig file, or None if none 48787d65558SVikram Narayanan """ 48887d65558SVikram Narayanan uname = command.OutputOneLine('git', 'config', '--global', 'user.name') 48987d65558SVikram Narayanan return uname 49087d65558SVikram Narayanan 49187d65558SVikram Narayanandef GetDefaultUserEmail(): 49287d65558SVikram Narayanan """Gets the user.email from the global .gitconfig file. 49387d65558SVikram Narayanan 49487d65558SVikram Narayanan Returns: 49587d65558SVikram Narayanan User's email found in .gitconfig file, or None if none 49687d65558SVikram Narayanan """ 49787d65558SVikram Narayanan uemail = command.OutputOneLine('git', 'config', '--global', 'user.email') 49887d65558SVikram Narayanan return uemail 49987d65558SVikram Narayanan 5000d24de9dSSimon Glassdef Setup(): 5010d24de9dSSimon Glass """Set up git utils, by reading the alias files.""" 5020d24de9dSSimon Glass # Check for a git alias file also 5030d24de9dSSimon Glass alias_fname = GetAliasFile() 5040d24de9dSSimon Glass if alias_fname: 5050d24de9dSSimon Glass settings.ReadGitAliases(alias_fname) 5060d24de9dSSimon Glass 507*5f6a1c42SSimon Glassdef GetHead(): 508*5f6a1c42SSimon Glass """Get the hash of the current HEAD 509*5f6a1c42SSimon Glass 510*5f6a1c42SSimon Glass Returns: 511*5f6a1c42SSimon Glass Hash of HEAD 512*5f6a1c42SSimon Glass """ 513*5f6a1c42SSimon Glass return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H') 514*5f6a1c42SSimon Glass 5150d24de9dSSimon Glassif __name__ == "__main__": 5160d24de9dSSimon Glass import doctest 5170d24de9dSSimon Glass 5180d24de9dSSimon Glass doctest.testmod() 519