xref: /rk3399_rockchip-uboot/tools/patman/gitutil.py (revision 1a4596601fd395f3afb8f82f3f840c5e00bdd57a)
10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors.
20d24de9dSSimon Glass#
3*1a459660SWolfgang 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
145f6a1c42SSimon Glassimport settings
155f6a1c42SSimon Glass
160d24de9dSSimon Glass
170d24de9dSSimon Glassdef CountCommitsToBranch():
180d24de9dSSimon Glass    """Returns number of commits between HEAD and the tracking branch.
190d24de9dSSimon Glass
200d24de9dSSimon Glass    This looks back to the tracking branch and works out the number of commits
210d24de9dSSimon Glass    since then.
220d24de9dSSimon Glass
230d24de9dSSimon Glass    Return:
240d24de9dSSimon Glass        Number of patches that exist on top of the branch
250d24de9dSSimon Glass    """
262386060cSAndreas Bießmann    pipe = [['git', 'log', '--no-color', '--oneline', '--no-decorate',
272386060cSAndreas Bießmann             '@{upstream}..'],
280d24de9dSSimon Glass            ['wc', '-l']]
29a10fd93cSSimon Glass    stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout
300d24de9dSSimon Glass    patch_count = int(stdout)
310d24de9dSSimon Glass    return patch_count
320d24de9dSSimon Glass
335f6a1c42SSimon Glassdef GetUpstream(git_dir, branch):
345f6a1c42SSimon Glass    """Returns the name of the upstream for a branch
355f6a1c42SSimon Glass
365f6a1c42SSimon Glass    Args:
375f6a1c42SSimon Glass        git_dir: Git directory containing repo
385f6a1c42SSimon Glass        branch: Name of branch
395f6a1c42SSimon Glass
405f6a1c42SSimon Glass    Returns:
415f6a1c42SSimon Glass        Name of upstream branch (e.g. 'upstream/master') or None if none
425f6a1c42SSimon Glass    """
43cce717a9SSimon Glass    try:
445f6a1c42SSimon Glass        remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
455f6a1c42SSimon Glass                                       'branch.%s.remote' % branch)
465f6a1c42SSimon Glass        merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
475f6a1c42SSimon Glass                                      'branch.%s.merge' % branch)
48cce717a9SSimon Glass    except:
49cce717a9SSimon Glass        return None
50cce717a9SSimon Glass
515f6a1c42SSimon Glass    if remote == '.':
525f6a1c42SSimon Glass        return merge
535f6a1c42SSimon Glass    elif remote and merge:
545f6a1c42SSimon Glass        leaf = merge.split('/')[-1]
555f6a1c42SSimon Glass        return '%s/%s' % (remote, leaf)
565f6a1c42SSimon Glass    else:
575f6a1c42SSimon Glass        raise ValueError, ("Cannot determine upstream branch for branch "
585f6a1c42SSimon Glass                "'%s' remote='%s', merge='%s'" % (branch, remote, merge))
595f6a1c42SSimon Glass
605f6a1c42SSimon Glass
615f6a1c42SSimon Glassdef GetRangeInBranch(git_dir, branch, include_upstream=False):
625f6a1c42SSimon Glass    """Returns an expression for the commits in the given branch.
635f6a1c42SSimon Glass
645f6a1c42SSimon Glass    Args:
655f6a1c42SSimon Glass        git_dir: Directory containing git repo
665f6a1c42SSimon Glass        branch: Name of branch
675f6a1c42SSimon Glass    Return:
685f6a1c42SSimon Glass        Expression in the form 'upstream..branch' which can be used to
69cce717a9SSimon Glass        access the commits. If the branch does not exist, returns None.
705f6a1c42SSimon Glass    """
715f6a1c42SSimon Glass    upstream = GetUpstream(git_dir, branch)
72cce717a9SSimon Glass    if not upstream:
73cce717a9SSimon Glass        return None
745f6a1c42SSimon Glass    return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch)
755f6a1c42SSimon Glass
765f6a1c42SSimon Glassdef CountCommitsInBranch(git_dir, branch, include_upstream=False):
775f6a1c42SSimon Glass    """Returns the number of commits in the given branch.
785f6a1c42SSimon Glass
795f6a1c42SSimon Glass    Args:
805f6a1c42SSimon Glass        git_dir: Directory containing git repo
815f6a1c42SSimon Glass        branch: Name of branch
825f6a1c42SSimon Glass    Return:
83cce717a9SSimon Glass        Number of patches that exist on top of the branch, or None if the
84cce717a9SSimon Glass        branch does not exist.
855f6a1c42SSimon Glass    """
865f6a1c42SSimon Glass    range_expr = GetRangeInBranch(git_dir, branch, include_upstream)
87cce717a9SSimon Glass    if not range_expr:
88cce717a9SSimon Glass        return None
892386060cSAndreas Bießmann    pipe = [['git', '--git-dir', git_dir, 'log', '--oneline', '--no-decorate',
902386060cSAndreas Bießmann             range_expr],
915f6a1c42SSimon Glass            ['wc', '-l']]
925f6a1c42SSimon Glass    result = command.RunPipe(pipe, capture=True, oneline=True)
935f6a1c42SSimon Glass    patch_count = int(result.stdout)
945f6a1c42SSimon Glass    return patch_count
955f6a1c42SSimon Glass
965f6a1c42SSimon Glassdef CountCommits(commit_range):
975f6a1c42SSimon Glass    """Returns the number of commits in the given range.
985f6a1c42SSimon Glass
995f6a1c42SSimon Glass    Args:
1005f6a1c42SSimon Glass        commit_range: Range of commits to count (e.g. 'HEAD..base')
1015f6a1c42SSimon Glass    Return:
1025f6a1c42SSimon Glass        Number of patches that exist on top of the branch
1035f6a1c42SSimon Glass    """
1042386060cSAndreas Bießmann    pipe = [['git', 'log', '--oneline', '--no-decorate', commit_range],
1055f6a1c42SSimon Glass            ['wc', '-l']]
1065f6a1c42SSimon Glass    stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout
1075f6a1c42SSimon Glass    patch_count = int(stdout)
1085f6a1c42SSimon Glass    return patch_count
1095f6a1c42SSimon Glass
1105f6a1c42SSimon Glassdef Checkout(commit_hash, git_dir=None, work_tree=None, force=False):
1115f6a1c42SSimon Glass    """Checkout the selected commit for this build
1125f6a1c42SSimon Glass
1135f6a1c42SSimon Glass    Args:
1145f6a1c42SSimon Glass        commit_hash: Commit hash to check out
1155f6a1c42SSimon Glass    """
1165f6a1c42SSimon Glass    pipe = ['git']
1175f6a1c42SSimon Glass    if git_dir:
1185f6a1c42SSimon Glass        pipe.extend(['--git-dir', git_dir])
1195f6a1c42SSimon Glass    if work_tree:
1205f6a1c42SSimon Glass        pipe.extend(['--work-tree', work_tree])
1215f6a1c42SSimon Glass    pipe.append('checkout')
1225f6a1c42SSimon Glass    if force:
1235f6a1c42SSimon Glass        pipe.append('-f')
1245f6a1c42SSimon Glass    pipe.append(commit_hash)
1255f6a1c42SSimon Glass    result = command.RunPipe([pipe], capture=True, raise_on_error=False)
1265f6a1c42SSimon Glass    if result.return_code != 0:
1275f6a1c42SSimon Glass        raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr)
1285f6a1c42SSimon Glass
1295f6a1c42SSimon Glassdef Clone(git_dir, output_dir):
1305f6a1c42SSimon Glass    """Checkout the selected commit for this build
1315f6a1c42SSimon Glass
1325f6a1c42SSimon Glass    Args:
1335f6a1c42SSimon Glass        commit_hash: Commit hash to check out
1345f6a1c42SSimon Glass    """
1355f6a1c42SSimon Glass    pipe = ['git', 'clone', git_dir, '.']
1365f6a1c42SSimon Glass    result = command.RunPipe([pipe], capture=True, cwd=output_dir)
1375f6a1c42SSimon Glass    if result.return_code != 0:
1385f6a1c42SSimon Glass        raise OSError, 'git clone: %s' % result.stderr
1395f6a1c42SSimon Glass
1405f6a1c42SSimon Glassdef Fetch(git_dir=None, work_tree=None):
1415f6a1c42SSimon Glass    """Fetch from the origin repo
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('fetch')
1525f6a1c42SSimon Glass    result = command.RunPipe([pipe], capture=True)
1535f6a1c42SSimon Glass    if result.return_code != 0:
1545f6a1c42SSimon Glass        raise OSError, 'git fetch: %s' % result.stderr
1555f6a1c42SSimon Glass
1560d24de9dSSimon Glassdef CreatePatches(start, count, series):
1570d24de9dSSimon Glass    """Create a series of patches from the top of the current branch.
1580d24de9dSSimon Glass
1590d24de9dSSimon Glass    The patch files are written to the current directory using
1600d24de9dSSimon Glass    git format-patch.
1610d24de9dSSimon Glass
1620d24de9dSSimon Glass    Args:
1630d24de9dSSimon Glass        start: Commit to start from: 0=HEAD, 1=next one, etc.
1640d24de9dSSimon Glass        count: number of commits to include
1650d24de9dSSimon Glass    Return:
1660d24de9dSSimon Glass        Filename of cover letter
1670d24de9dSSimon Glass        List of filenames of patch files
1680d24de9dSSimon Glass    """
1690d24de9dSSimon Glass    if series.get('version'):
1700d24de9dSSimon Glass        version = '%s ' % series['version']
1710d24de9dSSimon Glass    cmd = ['git', 'format-patch', '-M', '--signoff']
1720d24de9dSSimon Glass    if series.get('cover'):
1730d24de9dSSimon Glass        cmd.append('--cover-letter')
1740d24de9dSSimon Glass    prefix = series.GetPatchPrefix()
1750d24de9dSSimon Glass    if prefix:
1760d24de9dSSimon Glass        cmd += ['--subject-prefix=%s' % prefix]
1770d24de9dSSimon Glass    cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)]
1780d24de9dSSimon Glass
1790d24de9dSSimon Glass    stdout = command.RunList(cmd)
1800d24de9dSSimon Glass    files = stdout.splitlines()
1810d24de9dSSimon Glass
1820d24de9dSSimon Glass    # We have an extra file if there is a cover letter
1830d24de9dSSimon Glass    if series.get('cover'):
1840d24de9dSSimon Glass       return files[0], files[1:]
1850d24de9dSSimon Glass    else:
1860d24de9dSSimon Glass       return None, files
1870d24de9dSSimon Glass
1880d24de9dSSimon Glassdef ApplyPatch(verbose, fname):
1890d24de9dSSimon Glass    """Apply a patch with git am to test it
1900d24de9dSSimon Glass
1910d24de9dSSimon Glass    TODO: Convert these to use command, with stderr option
1920d24de9dSSimon Glass
1930d24de9dSSimon Glass    Args:
1940d24de9dSSimon Glass        fname: filename of patch file to apply
1950d24de9dSSimon Glass    """
1960d24de9dSSimon Glass    cmd = ['git', 'am', fname]
1970d24de9dSSimon Glass    pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1980d24de9dSSimon Glass            stderr=subprocess.PIPE)
1990d24de9dSSimon Glass    stdout, stderr = pipe.communicate()
2000d24de9dSSimon Glass    re_error = re.compile('^error: patch failed: (.+):(\d+)')
2010d24de9dSSimon Glass    for line in stderr.splitlines():
2020d24de9dSSimon Glass        if verbose:
2030d24de9dSSimon Glass            print line
2040d24de9dSSimon Glass        match = re_error.match(line)
2050d24de9dSSimon Glass        if match:
2060d24de9dSSimon Glass            print GetWarningMsg('warning', match.group(1), int(match.group(2)),
2070d24de9dSSimon Glass                    'Patch failed')
2080d24de9dSSimon Glass    return pipe.returncode == 0, stdout
2090d24de9dSSimon Glass
2100d24de9dSSimon Glassdef ApplyPatches(verbose, args, start_point):
2110d24de9dSSimon Glass    """Apply the patches with git am to make sure all is well
2120d24de9dSSimon Glass
2130d24de9dSSimon Glass    Args:
2140d24de9dSSimon Glass        verbose: Print out 'git am' output verbatim
2150d24de9dSSimon Glass        args: List of patch files to apply
2160d24de9dSSimon Glass        start_point: Number of commits back from HEAD to start applying.
2170d24de9dSSimon Glass            Normally this is len(args), but it can be larger if a start
2180d24de9dSSimon Glass            offset was given.
2190d24de9dSSimon Glass    """
2200d24de9dSSimon Glass    error_count = 0
2210d24de9dSSimon Glass    col = terminal.Color()
2220d24de9dSSimon Glass
2230d24de9dSSimon Glass    # Figure out our current position
2240d24de9dSSimon Glass    cmd = ['git', 'name-rev', 'HEAD', '--name-only']
2250d24de9dSSimon Glass    pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE)
2260d24de9dSSimon Glass    stdout, stderr = pipe.communicate()
2270d24de9dSSimon Glass    if pipe.returncode:
2280d24de9dSSimon Glass        str = 'Could not find current commit name'
2290d24de9dSSimon Glass        print col.Color(col.RED, str)
2300d24de9dSSimon Glass        print stdout
2310d24de9dSSimon Glass        return False
2320d24de9dSSimon Glass    old_head = stdout.splitlines()[0]
2330d24de9dSSimon Glass
2340d24de9dSSimon Glass    # Checkout the required start point
2350d24de9dSSimon Glass    cmd = ['git', 'checkout', 'HEAD~%d' % start_point]
2360d24de9dSSimon Glass    pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
2370d24de9dSSimon Glass            stderr=subprocess.PIPE)
2380d24de9dSSimon Glass    stdout, stderr = pipe.communicate()
2390d24de9dSSimon Glass    if pipe.returncode:
2400d24de9dSSimon Glass        str = 'Could not move to commit before patch series'
2410d24de9dSSimon Glass        print col.Color(col.RED, str)
2420d24de9dSSimon Glass        print stdout, stderr
2430d24de9dSSimon Glass        return False
2440d24de9dSSimon Glass
2450d24de9dSSimon Glass    # Apply all the patches
2460d24de9dSSimon Glass    for fname in args:
2470d24de9dSSimon Glass        ok, stdout = ApplyPatch(verbose, fname)
2480d24de9dSSimon Glass        if not ok:
2490d24de9dSSimon Glass            print col.Color(col.RED, 'git am returned errors for %s: will '
2500d24de9dSSimon Glass                    'skip this patch' % fname)
2510d24de9dSSimon Glass            if verbose:
2520d24de9dSSimon Glass                print stdout
2530d24de9dSSimon Glass            error_count += 1
2540d24de9dSSimon Glass            cmd = ['git', 'am', '--skip']
2550d24de9dSSimon Glass            pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE)
2560d24de9dSSimon Glass            stdout, stderr = pipe.communicate()
2570d24de9dSSimon Glass            if pipe.returncode != 0:
2580d24de9dSSimon Glass                print col.Color(col.RED, 'Unable to skip patch! Aborting...')
2590d24de9dSSimon Glass                print stdout
2600d24de9dSSimon Glass                break
2610d24de9dSSimon Glass
2620d24de9dSSimon Glass    # Return to our previous position
2630d24de9dSSimon Glass    cmd = ['git', 'checkout', old_head]
2640d24de9dSSimon Glass    pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2650d24de9dSSimon Glass    stdout, stderr = pipe.communicate()
2660d24de9dSSimon Glass    if pipe.returncode:
2670d24de9dSSimon Glass        print col.Color(col.RED, 'Could not move back to head commit')
2680d24de9dSSimon Glass        print stdout, stderr
2690d24de9dSSimon Glass    return error_count == 0
2700d24de9dSSimon Glass
271a1318f7cSSimon Glassdef BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True):
2720d24de9dSSimon Glass    """Build a list of email addresses based on an input list.
2730d24de9dSSimon Glass
2740d24de9dSSimon Glass    Takes a list of email addresses and aliases, and turns this into a list
2750d24de9dSSimon Glass    of only email address, by resolving any aliases that are present.
2760d24de9dSSimon Glass
2770d24de9dSSimon Glass    If the tag is given, then each email address is prepended with this
2780d24de9dSSimon Glass    tag and a space. If the tag starts with a minus sign (indicating a
2790d24de9dSSimon Glass    command line parameter) then the email address is quoted.
2800d24de9dSSimon Glass
2810d24de9dSSimon Glass    Args:
2820d24de9dSSimon Glass        in_list:        List of aliases/email addresses
2830d24de9dSSimon Glass        tag:            Text to put before each address
284a1318f7cSSimon Glass        alias:          Alias dictionary
285a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
286a1318f7cSSimon Glass                False to just print a message.
2870d24de9dSSimon Glass
2880d24de9dSSimon Glass    Returns:
2890d24de9dSSimon Glass        List of email addresses
2900d24de9dSSimon Glass
2910d24de9dSSimon Glass    >>> alias = {}
2920d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
2930d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
2940d24de9dSSimon Glass    >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
2950d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john']
2960d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
2970d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], None, alias)
2980d24de9dSSimon Glass    ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
2990d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], '--to', alias)
3000d24de9dSSimon Glass    ['--to "j.bloggs@napier.co.nz"', \
3010d24de9dSSimon Glass'--to "Mary Poppins <m.poppins@cloud.net>"']
3020d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], 'Cc', alias)
3030d24de9dSSimon Glass    ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
3040d24de9dSSimon Glass    """
3050d24de9dSSimon Glass    quote = '"' if tag and tag[0] == '-' else ''
3060d24de9dSSimon Glass    raw = []
3070d24de9dSSimon Glass    for item in in_list:
308a1318f7cSSimon Glass        raw += LookupEmail(item, alias, raise_on_error=raise_on_error)
3090d24de9dSSimon Glass    result = []
3100d24de9dSSimon Glass    for item in raw:
3110d24de9dSSimon Glass        if not item in result:
3120d24de9dSSimon Glass            result.append(item)
3130d24de9dSSimon Glass    if tag:
3140d24de9dSSimon Glass        return ['%s %s%s%s' % (tag, quote, email, quote) for email in result]
3150d24de9dSSimon Glass    return result
3160d24de9dSSimon Glass
317a1318f7cSSimon Glassdef EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
3186d819925SDoug Anderson        self_only=False, alias=None, in_reply_to=None):
3190d24de9dSSimon Glass    """Email a patch series.
3200d24de9dSSimon Glass
3210d24de9dSSimon Glass    Args:
3220d24de9dSSimon Glass        series: Series object containing destination info
3230d24de9dSSimon Glass        cover_fname: filename of cover letter
3240d24de9dSSimon Glass        args: list of filenames of patch files
3250d24de9dSSimon Glass        dry_run: Just return the command that would be run
326a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
327a1318f7cSSimon Glass                False to just print a message.
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
3306d819925SDoug Anderson        in_reply_to: If set we'll pass this to git as --in-reply-to.
3316d819925SDoug Anderson            Should be a message ID that this is in reply to.
3320d24de9dSSimon Glass
3330d24de9dSSimon Glass    Returns:
3340d24de9dSSimon Glass        Git command that was/would be run
3350d24de9dSSimon Glass
336a970048eSDoug Anderson    # For the duration of this doctest pretend that we ran patman with ./patman
337a970048eSDoug Anderson    >>> _old_argv0 = sys.argv[0]
338a970048eSDoug Anderson    >>> sys.argv[0] = './patman'
339a970048eSDoug Anderson
3400d24de9dSSimon Glass    >>> alias = {}
3410d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
3420d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
3430d24de9dSSimon Glass    >>> alias['mary'] = ['m.poppins@cloud.net']
3440d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john']
3450d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
3460d24de9dSSimon Glass    >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
3470d24de9dSSimon Glass    >>> series = series.Series()
3480d24de9dSSimon Glass    >>> series.to = ['fred']
3490d24de9dSSimon Glass    >>> series.cc = ['mary']
350a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
351a1318f7cSSimon Glass            False, alias)
3520d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3530d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
354a1318f7cSSimon Glass    >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \
355a1318f7cSSimon Glass            alias)
3560d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3570d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1'
3580d24de9dSSimon Glass    >>> series.cc = ['all']
359a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
360a1318f7cSSimon Glass            True, alias)
3610d24de9dSSimon Glass    'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
3620d24de9dSSimon Glass--cc-cmd cc-fname" cover p1 p2'
363a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
364a1318f7cSSimon Glass            False, alias)
3650d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3660d24de9dSSimon Glass"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
3670d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
368a970048eSDoug Anderson
369a970048eSDoug Anderson    # Restore argv[0] since we clobbered it.
370a970048eSDoug Anderson    >>> sys.argv[0] = _old_argv0
3710d24de9dSSimon Glass    """
372a1318f7cSSimon Glass    to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error)
3730d24de9dSSimon Glass    if not to:
3740d24de9dSSimon Glass        print ("No recipient, please add something like this to a commit\n"
3750d24de9dSSimon Glass            "Series-to: Fred Bloggs <f.blogs@napier.co.nz>")
3760d24de9dSSimon Glass        return
377a1318f7cSSimon Glass    cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error)
3780d24de9dSSimon Glass    if self_only:
379a1318f7cSSimon Glass        to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error)
3800d24de9dSSimon Glass        cc = []
3810d24de9dSSimon Glass    cmd = ['git', 'send-email', '--annotate']
3826d819925SDoug Anderson    if in_reply_to:
3836d819925SDoug Anderson        cmd.append('--in-reply-to="%s"' % in_reply_to)
3846d819925SDoug Anderson
3850d24de9dSSimon Glass    cmd += to
3860d24de9dSSimon Glass    cmd += cc
3870d24de9dSSimon Glass    cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)]
3880d24de9dSSimon Glass    if cover_fname:
3890d24de9dSSimon Glass        cmd.append(cover_fname)
3900d24de9dSSimon Glass    cmd += args
3910d24de9dSSimon Glass    str = ' '.join(cmd)
3920d24de9dSSimon Glass    if not dry_run:
3930d24de9dSSimon Glass        os.system(str)
3940d24de9dSSimon Glass    return str
3950d24de9dSSimon Glass
3960d24de9dSSimon Glass
397a1318f7cSSimon Glassdef LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0):
3980d24de9dSSimon Glass    """If an email address is an alias, look it up and return the full name
3990d24de9dSSimon Glass
4000d24de9dSSimon Glass    TODO: Why not just use git's own alias feature?
4010d24de9dSSimon Glass
4020d24de9dSSimon Glass    Args:
4030d24de9dSSimon Glass        lookup_name: Alias or email address to look up
404a1318f7cSSimon Glass        alias: Dictionary containing aliases (None to use settings default)
405a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
406a1318f7cSSimon Glass                False to just print a message.
4070d24de9dSSimon Glass
4080d24de9dSSimon Glass    Returns:
4090d24de9dSSimon Glass        tuple:
4100d24de9dSSimon Glass            list containing a list of email addresses
4110d24de9dSSimon Glass
4120d24de9dSSimon Glass    Raises:
4130d24de9dSSimon Glass        OSError if a recursive alias reference was found
4140d24de9dSSimon Glass        ValueError if an alias was not found
4150d24de9dSSimon Glass
4160d24de9dSSimon Glass    >>> alias = {}
4170d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
4180d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
4190d24de9dSSimon Glass    >>> alias['mary'] = ['m.poppins@cloud.net']
4200d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
4210d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
4220d24de9dSSimon Glass    >>> alias['loop'] = ['other', 'john', '   mary   ']
4230d24de9dSSimon Glass    >>> alias['other'] = ['loop', 'john', '   mary   ']
4240d24de9dSSimon Glass    >>> LookupEmail('mary', alias)
4250d24de9dSSimon Glass    ['m.poppins@cloud.net']
4260d24de9dSSimon Glass    >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias)
4270d24de9dSSimon Glass    ['arthur.wellesley@howe.ro.uk']
4280d24de9dSSimon Glass    >>> LookupEmail('boys', alias)
4290d24de9dSSimon Glass    ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
4300d24de9dSSimon Glass    >>> LookupEmail('all', alias)
4310d24de9dSSimon Glass    ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
4320d24de9dSSimon Glass    >>> LookupEmail('odd', alias)
4330d24de9dSSimon Glass    Traceback (most recent call last):
4340d24de9dSSimon Glass    ...
4350d24de9dSSimon Glass    ValueError: Alias 'odd' not found
4360d24de9dSSimon Glass    >>> LookupEmail('loop', alias)
4370d24de9dSSimon Glass    Traceback (most recent call last):
4380d24de9dSSimon Glass    ...
4390d24de9dSSimon Glass    OSError: Recursive email alias at 'other'
440a1318f7cSSimon Glass    >>> LookupEmail('odd', alias, raise_on_error=False)
441a1318f7cSSimon Glass    \033[1;31mAlias 'odd' not found\033[0m
442a1318f7cSSimon Glass    []
443a1318f7cSSimon Glass    >>> # In this case the loop part will effectively be ignored.
444a1318f7cSSimon Glass    >>> LookupEmail('loop', alias, raise_on_error=False)
445a1318f7cSSimon Glass    \033[1;31mRecursive email alias at 'other'\033[0m
446a1318f7cSSimon Glass    \033[1;31mRecursive email alias at 'john'\033[0m
447a1318f7cSSimon Glass    \033[1;31mRecursive email alias at 'mary'\033[0m
448a1318f7cSSimon Glass    ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
4490d24de9dSSimon Glass    """
4500d24de9dSSimon Glass    if not alias:
4510d24de9dSSimon Glass        alias = settings.alias
4520d24de9dSSimon Glass    lookup_name = lookup_name.strip()
4530d24de9dSSimon Glass    if '@' in lookup_name: # Perhaps a real email address
4540d24de9dSSimon Glass        return [lookup_name]
4550d24de9dSSimon Glass
4560d24de9dSSimon Glass    lookup_name = lookup_name.lower()
457a1318f7cSSimon Glass    col = terminal.Color()
4580d24de9dSSimon Glass
4590d24de9dSSimon Glass    out_list = []
460a1318f7cSSimon Glass    if level > 10:
461a1318f7cSSimon Glass        msg = "Recursive email alias at '%s'" % lookup_name
462a1318f7cSSimon Glass        if raise_on_error:
463a1318f7cSSimon Glass            raise OSError, msg
464a1318f7cSSimon Glass        else:
465a1318f7cSSimon Glass            print col.Color(col.RED, msg)
466a1318f7cSSimon Glass            return out_list
467a1318f7cSSimon Glass
4680d24de9dSSimon Glass    if lookup_name:
4690d24de9dSSimon Glass        if not lookup_name in alias:
470a1318f7cSSimon Glass            msg = "Alias '%s' not found" % lookup_name
471a1318f7cSSimon Glass            if raise_on_error:
472a1318f7cSSimon Glass                raise ValueError, msg
473a1318f7cSSimon Glass            else:
474a1318f7cSSimon Glass                print col.Color(col.RED, msg)
475a1318f7cSSimon Glass                return out_list
4760d24de9dSSimon Glass        for item in alias[lookup_name]:
477a1318f7cSSimon Glass            todo = LookupEmail(item, alias, raise_on_error, level + 1)
4780d24de9dSSimon Glass            for new_item in todo:
4790d24de9dSSimon Glass                if not new_item in out_list:
4800d24de9dSSimon Glass                    out_list.append(new_item)
4810d24de9dSSimon Glass
4820d24de9dSSimon Glass    #print "No match for alias '%s'" % lookup_name
4830d24de9dSSimon Glass    return out_list
4840d24de9dSSimon Glass
4850d24de9dSSimon Glassdef GetTopLevel():
4860d24de9dSSimon Glass    """Return name of top-level directory for this git repo.
4870d24de9dSSimon Glass
4880d24de9dSSimon Glass    Returns:
4890d24de9dSSimon Glass        Full path to git top-level directory
4900d24de9dSSimon Glass
4910d24de9dSSimon Glass    This test makes sure that we are running tests in the right subdir
4920d24de9dSSimon Glass
493a970048eSDoug Anderson    >>> os.path.realpath(os.path.dirname(__file__)) == \
494a970048eSDoug Anderson            os.path.join(GetTopLevel(), 'tools', 'patman')
4950d24de9dSSimon Glass    True
4960d24de9dSSimon Glass    """
4970d24de9dSSimon Glass    return command.OutputOneLine('git', 'rev-parse', '--show-toplevel')
4980d24de9dSSimon Glass
4990d24de9dSSimon Glassdef GetAliasFile():
5000d24de9dSSimon Glass    """Gets the name of the git alias file.
5010d24de9dSSimon Glass
5020d24de9dSSimon Glass    Returns:
5030d24de9dSSimon Glass        Filename of git alias file, or None if none
5040d24de9dSSimon Glass    """
505dc191505SSimon Glass    fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile',
506dc191505SSimon Glass            raise_on_error=False)
5070d24de9dSSimon Glass    if fname:
5080d24de9dSSimon Glass        fname = os.path.join(GetTopLevel(), fname.strip())
5090d24de9dSSimon Glass    return fname
5100d24de9dSSimon Glass
51187d65558SVikram Narayanandef GetDefaultUserName():
51287d65558SVikram Narayanan    """Gets the user.name from .gitconfig file.
51387d65558SVikram Narayanan
51487d65558SVikram Narayanan    Returns:
51587d65558SVikram Narayanan        User name found in .gitconfig file, or None if none
51687d65558SVikram Narayanan    """
51787d65558SVikram Narayanan    uname = command.OutputOneLine('git', 'config', '--global', 'user.name')
51887d65558SVikram Narayanan    return uname
51987d65558SVikram Narayanan
52087d65558SVikram Narayanandef GetDefaultUserEmail():
52187d65558SVikram Narayanan    """Gets the user.email from the global .gitconfig file.
52287d65558SVikram Narayanan
52387d65558SVikram Narayanan    Returns:
52487d65558SVikram Narayanan        User's email found in .gitconfig file, or None if none
52587d65558SVikram Narayanan    """
52687d65558SVikram Narayanan    uemail = command.OutputOneLine('git', 'config', '--global', 'user.email')
52787d65558SVikram Narayanan    return uemail
52887d65558SVikram Narayanan
5290d24de9dSSimon Glassdef Setup():
5300d24de9dSSimon Glass    """Set up git utils, by reading the alias files."""
5310d24de9dSSimon Glass    # Check for a git alias file also
5320d24de9dSSimon Glass    alias_fname = GetAliasFile()
5330d24de9dSSimon Glass    if alias_fname:
5340d24de9dSSimon Glass        settings.ReadGitAliases(alias_fname)
5350d24de9dSSimon Glass
5365f6a1c42SSimon Glassdef GetHead():
5375f6a1c42SSimon Glass    """Get the hash of the current HEAD
5385f6a1c42SSimon Glass
5395f6a1c42SSimon Glass    Returns:
5405f6a1c42SSimon Glass        Hash of HEAD
5415f6a1c42SSimon Glass    """
5425f6a1c42SSimon Glass    return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H')
5435f6a1c42SSimon Glass
5440d24de9dSSimon Glassif __name__ == "__main__":
5450d24de9dSSimon Glass    import doctest
5460d24de9dSSimon Glass
5470d24de9dSSimon Glass    doctest.testmod()
548