xref: /rk3399_rockchip-uboot/tools/patman/gitutil.py (revision 5f6a1c420070221e2fdbe81d9a8af8bcd3c05fbb)
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