xref: /rk3399_rockchip-uboot/tools/patman/gitutil.py (revision ddaf5c8f3030050fcd356a1e49e3ee8f8f52c6d4)
10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors.
20d24de9dSSimon Glass#
31a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
40d24de9dSSimon Glass#
50d24de9dSSimon Glass
60d24de9dSSimon Glassimport command
70d24de9dSSimon Glassimport re
80d24de9dSSimon Glassimport os
90d24de9dSSimon Glassimport series
100d24de9dSSimon Glassimport subprocess
110d24de9dSSimon Glassimport sys
120d24de9dSSimon Glassimport terminal
130d24de9dSSimon Glass
14757f64a8SSimon Glassimport checkpatch
155f6a1c42SSimon Glassimport settings
165f6a1c42SSimon Glass
17e49f14afSSimon Glass# True to use --no-decorate - we check this in Setup()
18e49f14afSSimon Glassuse_no_decorate = True
19e49f14afSSimon Glass
20cda2a611SSimon Glassdef LogCmd(commit_range, git_dir=None, oneline=False, reverse=False,
21cda2a611SSimon Glass           count=None):
22cda2a611SSimon Glass    """Create a command to perform a 'git log'
23cda2a611SSimon Glass
24cda2a611SSimon Glass    Args:
25cda2a611SSimon Glass        commit_range: Range expression to use for log, None for none
26cda2a611SSimon Glass        git_dir: Path to git repositiory (None to use default)
27cda2a611SSimon Glass        oneline: True to use --oneline, else False
28cda2a611SSimon Glass        reverse: True to reverse the log (--reverse)
29cda2a611SSimon Glass        count: Number of commits to list, or None for no limit
30cda2a611SSimon Glass    Return:
31cda2a611SSimon Glass        List containing command and arguments to run
32cda2a611SSimon Glass    """
33cda2a611SSimon Glass    cmd = ['git']
34cda2a611SSimon Glass    if git_dir:
35cda2a611SSimon Glass        cmd += ['--git-dir', git_dir]
369447a6b2SSimon Glass    cmd += ['--no-pager', 'log', '--no-color']
37cda2a611SSimon Glass    if oneline:
38cda2a611SSimon Glass        cmd.append('--oneline')
39e49f14afSSimon Glass    if use_no_decorate:
40cda2a611SSimon Glass        cmd.append('--no-decorate')
41042a732cSSimon Glass    if reverse:
42042a732cSSimon Glass        cmd.append('--reverse')
43cda2a611SSimon Glass    if count is not None:
44cda2a611SSimon Glass        cmd.append('-n%d' % count)
45cda2a611SSimon Glass    if commit_range:
46cda2a611SSimon Glass        cmd.append(commit_range)
47cda2a611SSimon Glass    return cmd
480d24de9dSSimon Glass
490d24de9dSSimon Glassdef CountCommitsToBranch():
500d24de9dSSimon Glass    """Returns number of commits between HEAD and the tracking branch.
510d24de9dSSimon Glass
520d24de9dSSimon Glass    This looks back to the tracking branch and works out the number of commits
530d24de9dSSimon Glass    since then.
540d24de9dSSimon Glass
550d24de9dSSimon Glass    Return:
560d24de9dSSimon Glass        Number of patches that exist on top of the branch
570d24de9dSSimon Glass    """
58cda2a611SSimon Glass    pipe = [LogCmd('@{upstream}..', oneline=True),
590d24de9dSSimon Glass            ['wc', '-l']]
60a10fd93cSSimon Glass    stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout
610d24de9dSSimon Glass    patch_count = int(stdout)
620d24de9dSSimon Glass    return patch_count
630d24de9dSSimon Glass
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)
155*ddaf5c8fSSimon Glass    result = command.RunPipe([pipe], capture=True, raise_on_error=False,
156*ddaf5c8fSSimon Glass                             capture_stderr=True)
1575f6a1c42SSimon Glass    if result.return_code != 0:
1585f6a1c42SSimon Glass        raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr)
1595f6a1c42SSimon Glass
1605f6a1c42SSimon Glassdef Clone(git_dir, output_dir):
1615f6a1c42SSimon Glass    """Checkout the selected commit for this build
1625f6a1c42SSimon Glass
1635f6a1c42SSimon Glass    Args:
1645f6a1c42SSimon Glass        commit_hash: Commit hash to check out
1655f6a1c42SSimon Glass    """
1665f6a1c42SSimon Glass    pipe = ['git', 'clone', git_dir, '.']
167*ddaf5c8fSSimon Glass    result = command.RunPipe([pipe], capture=True, cwd=output_dir,
168*ddaf5c8fSSimon Glass                             capture_stderr=True)
1695f6a1c42SSimon Glass    if result.return_code != 0:
1705f6a1c42SSimon Glass        raise OSError, 'git clone: %s' % result.stderr
1715f6a1c42SSimon Glass
1725f6a1c42SSimon Glassdef Fetch(git_dir=None, work_tree=None):
1735f6a1c42SSimon Glass    """Fetch from the origin repo
1745f6a1c42SSimon Glass
1755f6a1c42SSimon Glass    Args:
1765f6a1c42SSimon Glass        commit_hash: Commit hash to check out
1775f6a1c42SSimon Glass    """
1785f6a1c42SSimon Glass    pipe = ['git']
1795f6a1c42SSimon Glass    if git_dir:
1805f6a1c42SSimon Glass        pipe.extend(['--git-dir', git_dir])
1815f6a1c42SSimon Glass    if work_tree:
1825f6a1c42SSimon Glass        pipe.extend(['--work-tree', work_tree])
1835f6a1c42SSimon Glass    pipe.append('fetch')
184*ddaf5c8fSSimon Glass    result = command.RunPipe([pipe], capture=True, capture_stderr=True)
1855f6a1c42SSimon Glass    if result.return_code != 0:
1865f6a1c42SSimon Glass        raise OSError, 'git fetch: %s' % result.stderr
1875f6a1c42SSimon Glass
1880d24de9dSSimon Glassdef CreatePatches(start, count, series):
1890d24de9dSSimon Glass    """Create a series of patches from the top of the current branch.
1900d24de9dSSimon Glass
1910d24de9dSSimon Glass    The patch files are written to the current directory using
1920d24de9dSSimon Glass    git format-patch.
1930d24de9dSSimon Glass
1940d24de9dSSimon Glass    Args:
1950d24de9dSSimon Glass        start: Commit to start from: 0=HEAD, 1=next one, etc.
1960d24de9dSSimon Glass        count: number of commits to include
1970d24de9dSSimon Glass    Return:
1980d24de9dSSimon Glass        Filename of cover letter
1990d24de9dSSimon Glass        List of filenames of patch files
2000d24de9dSSimon Glass    """
2010d24de9dSSimon Glass    if series.get('version'):
2020d24de9dSSimon Glass        version = '%s ' % series['version']
2030d24de9dSSimon Glass    cmd = ['git', 'format-patch', '-M', '--signoff']
2040d24de9dSSimon Glass    if series.get('cover'):
2050d24de9dSSimon Glass        cmd.append('--cover-letter')
2060d24de9dSSimon Glass    prefix = series.GetPatchPrefix()
2070d24de9dSSimon Glass    if prefix:
2080d24de9dSSimon Glass        cmd += ['--subject-prefix=%s' % prefix]
2090d24de9dSSimon Glass    cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)]
2100d24de9dSSimon Glass
2110d24de9dSSimon Glass    stdout = command.RunList(cmd)
2120d24de9dSSimon Glass    files = stdout.splitlines()
2130d24de9dSSimon Glass
2140d24de9dSSimon Glass    # We have an extra file if there is a cover letter
2150d24de9dSSimon Glass    if series.get('cover'):
2160d24de9dSSimon Glass       return files[0], files[1:]
2170d24de9dSSimon Glass    else:
2180d24de9dSSimon Glass       return None, files
2190d24de9dSSimon Glass
220a1318f7cSSimon Glassdef BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True):
2210d24de9dSSimon Glass    """Build a list of email addresses based on an input list.
2220d24de9dSSimon Glass
2230d24de9dSSimon Glass    Takes a list of email addresses and aliases, and turns this into a list
2240d24de9dSSimon Glass    of only email address, by resolving any aliases that are present.
2250d24de9dSSimon Glass
2260d24de9dSSimon Glass    If the tag is given, then each email address is prepended with this
2270d24de9dSSimon Glass    tag and a space. If the tag starts with a minus sign (indicating a
2280d24de9dSSimon Glass    command line parameter) then the email address is quoted.
2290d24de9dSSimon Glass
2300d24de9dSSimon Glass    Args:
2310d24de9dSSimon Glass        in_list:        List of aliases/email addresses
2320d24de9dSSimon Glass        tag:            Text to put before each address
233a1318f7cSSimon Glass        alias:          Alias dictionary
234a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
235a1318f7cSSimon Glass                False to just print a message.
2360d24de9dSSimon Glass
2370d24de9dSSimon Glass    Returns:
2380d24de9dSSimon Glass        List of email addresses
2390d24de9dSSimon Glass
2400d24de9dSSimon Glass    >>> alias = {}
2410d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
2420d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
2430d24de9dSSimon Glass    >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
2440d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john']
2450d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
2460d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], None, alias)
2470d24de9dSSimon Glass    ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
2480d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], '--to', alias)
2490d24de9dSSimon Glass    ['--to "j.bloggs@napier.co.nz"', \
2500d24de9dSSimon Glass'--to "Mary Poppins <m.poppins@cloud.net>"']
2510d24de9dSSimon Glass    >>> BuildEmailList(['john', 'mary'], 'Cc', alias)
2520d24de9dSSimon Glass    ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
2530d24de9dSSimon Glass    """
2540d24de9dSSimon Glass    quote = '"' if tag and tag[0] == '-' else ''
2550d24de9dSSimon Glass    raw = []
2560d24de9dSSimon Glass    for item in in_list:
257a1318f7cSSimon Glass        raw += LookupEmail(item, alias, raise_on_error=raise_on_error)
2580d24de9dSSimon Glass    result = []
2590d24de9dSSimon Glass    for item in raw:
2600d24de9dSSimon Glass        if not item in result:
2610d24de9dSSimon Glass            result.append(item)
2620d24de9dSSimon Glass    if tag:
2630d24de9dSSimon Glass        return ['%s %s%s%s' % (tag, quote, email, quote) for email in result]
2640d24de9dSSimon Glass    return result
2650d24de9dSSimon Glass
266a1318f7cSSimon Glassdef EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
2676d819925SDoug Anderson        self_only=False, alias=None, in_reply_to=None):
2680d24de9dSSimon Glass    """Email a patch series.
2690d24de9dSSimon Glass
2700d24de9dSSimon Glass    Args:
2710d24de9dSSimon Glass        series: Series object containing destination info
2720d24de9dSSimon Glass        cover_fname: filename of cover letter
2730d24de9dSSimon Glass        args: list of filenames of patch files
2740d24de9dSSimon Glass        dry_run: Just return the command that would be run
275a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
276a1318f7cSSimon Glass                False to just print a message.
2770d24de9dSSimon Glass        cc_fname: Filename of Cc file for per-commit Cc
2780d24de9dSSimon Glass        self_only: True to just email to yourself as a test
2796d819925SDoug Anderson        in_reply_to: If set we'll pass this to git as --in-reply-to.
2806d819925SDoug Anderson            Should be a message ID that this is in reply to.
2810d24de9dSSimon Glass
2820d24de9dSSimon Glass    Returns:
2830d24de9dSSimon Glass        Git command that was/would be run
2840d24de9dSSimon Glass
285a970048eSDoug Anderson    # For the duration of this doctest pretend that we ran patman with ./patman
286a970048eSDoug Anderson    >>> _old_argv0 = sys.argv[0]
287a970048eSDoug Anderson    >>> sys.argv[0] = './patman'
288a970048eSDoug Anderson
2890d24de9dSSimon Glass    >>> alias = {}
2900d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
2910d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
2920d24de9dSSimon Glass    >>> alias['mary'] = ['m.poppins@cloud.net']
2930d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john']
2940d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
2950d24de9dSSimon Glass    >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
2960d24de9dSSimon Glass    >>> series = series.Series()
2970d24de9dSSimon Glass    >>> series.to = ['fred']
2980d24de9dSSimon Glass    >>> series.cc = ['mary']
299a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
300a1318f7cSSimon Glass            False, alias)
3010d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3020d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
303a1318f7cSSimon Glass    >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \
304a1318f7cSSimon Glass            alias)
3050d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3060d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1'
3070d24de9dSSimon Glass    >>> series.cc = ['all']
308a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
309a1318f7cSSimon Glass            True, alias)
3100d24de9dSSimon Glass    'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
3110d24de9dSSimon Glass--cc-cmd cc-fname" cover p1 p2'
312a1318f7cSSimon Glass    >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
313a1318f7cSSimon Glass            False, alias)
3140d24de9dSSimon Glass    'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
3150d24de9dSSimon Glass"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
3160d24de9dSSimon Glass"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
317a970048eSDoug Anderson
318a970048eSDoug Anderson    # Restore argv[0] since we clobbered it.
319a970048eSDoug Anderson    >>> sys.argv[0] = _old_argv0
3200d24de9dSSimon Glass    """
321a1318f7cSSimon Glass    to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error)
3220d24de9dSSimon Glass    if not to:
323ee860c60SMasahiro Yamada        git_config_to = command.Output('git', 'config', 'sendemail.to')
324ee860c60SMasahiro Yamada        if not git_config_to:
325ee860c60SMasahiro Yamada            print ("No recipient.\n"
326ee860c60SMasahiro Yamada                   "Please add something like this to a commit\n"
327ee860c60SMasahiro Yamada                   "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n"
328ee860c60SMasahiro Yamada                   "Or do something like this\n"
329ee860c60SMasahiro Yamada                   "git config sendemail.to u-boot@lists.denx.de")
3300d24de9dSSimon Glass            return
331a1318f7cSSimon Glass    cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error)
3320d24de9dSSimon Glass    if self_only:
333a1318f7cSSimon Glass        to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error)
3340d24de9dSSimon Glass        cc = []
3350d24de9dSSimon Glass    cmd = ['git', 'send-email', '--annotate']
3366d819925SDoug Anderson    if in_reply_to:
3376d819925SDoug Anderson        cmd.append('--in-reply-to="%s"' % in_reply_to)
3386d819925SDoug Anderson
3390d24de9dSSimon Glass    cmd += to
3400d24de9dSSimon Glass    cmd += cc
3410d24de9dSSimon Glass    cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)]
3420d24de9dSSimon Glass    if cover_fname:
3430d24de9dSSimon Glass        cmd.append(cover_fname)
3440d24de9dSSimon Glass    cmd += args
3450d24de9dSSimon Glass    str = ' '.join(cmd)
3460d24de9dSSimon Glass    if not dry_run:
3470d24de9dSSimon Glass        os.system(str)
3480d24de9dSSimon Glass    return str
3490d24de9dSSimon Glass
3500d24de9dSSimon Glass
351a1318f7cSSimon Glassdef LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0):
3520d24de9dSSimon Glass    """If an email address is an alias, look it up and return the full name
3530d24de9dSSimon Glass
3540d24de9dSSimon Glass    TODO: Why not just use git's own alias feature?
3550d24de9dSSimon Glass
3560d24de9dSSimon Glass    Args:
3570d24de9dSSimon Glass        lookup_name: Alias or email address to look up
358a1318f7cSSimon Glass        alias: Dictionary containing aliases (None to use settings default)
359a1318f7cSSimon Glass        raise_on_error: True to raise an error when an alias fails to match,
360a1318f7cSSimon Glass                False to just print a message.
3610d24de9dSSimon Glass
3620d24de9dSSimon Glass    Returns:
3630d24de9dSSimon Glass        tuple:
3640d24de9dSSimon Glass            list containing a list of email addresses
3650d24de9dSSimon Glass
3660d24de9dSSimon Glass    Raises:
3670d24de9dSSimon Glass        OSError if a recursive alias reference was found
3680d24de9dSSimon Glass        ValueError if an alias was not found
3690d24de9dSSimon Glass
3700d24de9dSSimon Glass    >>> alias = {}
3710d24de9dSSimon Glass    >>> alias['fred'] = ['f.bloggs@napier.co.nz']
3720d24de9dSSimon Glass    >>> alias['john'] = ['j.bloggs@napier.co.nz']
3730d24de9dSSimon Glass    >>> alias['mary'] = ['m.poppins@cloud.net']
3740d24de9dSSimon Glass    >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
3750d24de9dSSimon Glass    >>> alias['all'] = ['fred ', 'john', '   mary   ']
3760d24de9dSSimon Glass    >>> alias['loop'] = ['other', 'john', '   mary   ']
3770d24de9dSSimon Glass    >>> alias['other'] = ['loop', 'john', '   mary   ']
3780d24de9dSSimon Glass    >>> LookupEmail('mary', alias)
3790d24de9dSSimon Glass    ['m.poppins@cloud.net']
3800d24de9dSSimon Glass    >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias)
3810d24de9dSSimon Glass    ['arthur.wellesley@howe.ro.uk']
3820d24de9dSSimon Glass    >>> LookupEmail('boys', alias)
3830d24de9dSSimon Glass    ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
3840d24de9dSSimon Glass    >>> LookupEmail('all', alias)
3850d24de9dSSimon Glass    ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
3860d24de9dSSimon Glass    >>> LookupEmail('odd', alias)
3870d24de9dSSimon Glass    Traceback (most recent call last):
3880d24de9dSSimon Glass    ...
3890d24de9dSSimon Glass    ValueError: Alias 'odd' not found
3900d24de9dSSimon Glass    >>> LookupEmail('loop', alias)
3910d24de9dSSimon Glass    Traceback (most recent call last):
3920d24de9dSSimon Glass    ...
3930d24de9dSSimon Glass    OSError: Recursive email alias at 'other'
394a1318f7cSSimon Glass    >>> LookupEmail('odd', alias, raise_on_error=False)
395e752edcbSSimon Glass    Alias 'odd' not found
396a1318f7cSSimon Glass    []
397a1318f7cSSimon Glass    >>> # In this case the loop part will effectively be ignored.
398a1318f7cSSimon Glass    >>> LookupEmail('loop', alias, raise_on_error=False)
399e752edcbSSimon Glass    Recursive email alias at 'other'
400e752edcbSSimon Glass    Recursive email alias at 'john'
401e752edcbSSimon Glass    Recursive email alias at 'mary'
402a1318f7cSSimon Glass    ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
4030d24de9dSSimon Glass    """
4040d24de9dSSimon Glass    if not alias:
4050d24de9dSSimon Glass        alias = settings.alias
4060d24de9dSSimon Glass    lookup_name = lookup_name.strip()
4070d24de9dSSimon Glass    if '@' in lookup_name: # Perhaps a real email address
4080d24de9dSSimon Glass        return [lookup_name]
4090d24de9dSSimon Glass
4100d24de9dSSimon Glass    lookup_name = lookup_name.lower()
411a1318f7cSSimon Glass    col = terminal.Color()
4120d24de9dSSimon Glass
4130d24de9dSSimon Glass    out_list = []
414a1318f7cSSimon Glass    if level > 10:
415a1318f7cSSimon Glass        msg = "Recursive email alias at '%s'" % lookup_name
416a1318f7cSSimon Glass        if raise_on_error:
417a1318f7cSSimon Glass            raise OSError, msg
418a1318f7cSSimon Glass        else:
419a1318f7cSSimon Glass            print col.Color(col.RED, msg)
420a1318f7cSSimon Glass            return out_list
421a1318f7cSSimon Glass
4220d24de9dSSimon Glass    if lookup_name:
4230d24de9dSSimon Glass        if not lookup_name in alias:
424a1318f7cSSimon Glass            msg = "Alias '%s' not found" % lookup_name
425a1318f7cSSimon Glass            if raise_on_error:
426a1318f7cSSimon Glass                raise ValueError, msg
427a1318f7cSSimon Glass            else:
428a1318f7cSSimon Glass                print col.Color(col.RED, msg)
429a1318f7cSSimon Glass                return out_list
4300d24de9dSSimon Glass        for item in alias[lookup_name]:
431a1318f7cSSimon Glass            todo = LookupEmail(item, alias, raise_on_error, level + 1)
4320d24de9dSSimon Glass            for new_item in todo:
4330d24de9dSSimon Glass                if not new_item in out_list:
4340d24de9dSSimon Glass                    out_list.append(new_item)
4350d24de9dSSimon Glass
4360d24de9dSSimon Glass    #print "No match for alias '%s'" % lookup_name
4370d24de9dSSimon Glass    return out_list
4380d24de9dSSimon Glass
4390d24de9dSSimon Glassdef GetTopLevel():
4400d24de9dSSimon Glass    """Return name of top-level directory for this git repo.
4410d24de9dSSimon Glass
4420d24de9dSSimon Glass    Returns:
4430d24de9dSSimon Glass        Full path to git top-level directory
4440d24de9dSSimon Glass
4450d24de9dSSimon Glass    This test makes sure that we are running tests in the right subdir
4460d24de9dSSimon Glass
447a970048eSDoug Anderson    >>> os.path.realpath(os.path.dirname(__file__)) == \
448a970048eSDoug Anderson            os.path.join(GetTopLevel(), 'tools', 'patman')
4490d24de9dSSimon Glass    True
4500d24de9dSSimon Glass    """
4510d24de9dSSimon Glass    return command.OutputOneLine('git', 'rev-parse', '--show-toplevel')
4520d24de9dSSimon Glass
4530d24de9dSSimon Glassdef GetAliasFile():
4540d24de9dSSimon Glass    """Gets the name of the git alias file.
4550d24de9dSSimon Glass
4560d24de9dSSimon Glass    Returns:
4570d24de9dSSimon Glass        Filename of git alias file, or None if none
4580d24de9dSSimon Glass    """
459dc191505SSimon Glass    fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile',
460dc191505SSimon Glass            raise_on_error=False)
4610d24de9dSSimon Glass    if fname:
4620d24de9dSSimon Glass        fname = os.path.join(GetTopLevel(), fname.strip())
4630d24de9dSSimon Glass    return fname
4640d24de9dSSimon Glass
46587d65558SVikram Narayanandef GetDefaultUserName():
46687d65558SVikram Narayanan    """Gets the user.name from .gitconfig file.
46787d65558SVikram Narayanan
46887d65558SVikram Narayanan    Returns:
46987d65558SVikram Narayanan        User name found in .gitconfig file, or None if none
47087d65558SVikram Narayanan    """
47187d65558SVikram Narayanan    uname = command.OutputOneLine('git', 'config', '--global', 'user.name')
47287d65558SVikram Narayanan    return uname
47387d65558SVikram Narayanan
47487d65558SVikram Narayanandef GetDefaultUserEmail():
47587d65558SVikram Narayanan    """Gets the user.email from the global .gitconfig file.
47687d65558SVikram Narayanan
47787d65558SVikram Narayanan    Returns:
47887d65558SVikram Narayanan        User's email found in .gitconfig file, or None if none
47987d65558SVikram Narayanan    """
48087d65558SVikram Narayanan    uemail = command.OutputOneLine('git', 'config', '--global', 'user.email')
48187d65558SVikram Narayanan    return uemail
48287d65558SVikram Narayanan
4830d24de9dSSimon Glassdef Setup():
4840d24de9dSSimon Glass    """Set up git utils, by reading the alias files."""
4850d24de9dSSimon Glass    # Check for a git alias file also
4860b703dbcSSimon Glass    global use_no_decorate
4870b703dbcSSimon Glass
4880d24de9dSSimon Glass    alias_fname = GetAliasFile()
4890d24de9dSSimon Glass    if alias_fname:
4900d24de9dSSimon Glass        settings.ReadGitAliases(alias_fname)
491e49f14afSSimon Glass    cmd = LogCmd(None, count=0)
492e49f14afSSimon Glass    use_no_decorate = (command.RunPipe([cmd], raise_on_error=False)
493e49f14afSSimon Glass                       .return_code == 0)
4940d24de9dSSimon Glass
4955f6a1c42SSimon Glassdef GetHead():
4965f6a1c42SSimon Glass    """Get the hash of the current HEAD
4975f6a1c42SSimon Glass
4985f6a1c42SSimon Glass    Returns:
4995f6a1c42SSimon Glass        Hash of HEAD
5005f6a1c42SSimon Glass    """
5015f6a1c42SSimon Glass    return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H')
5025f6a1c42SSimon Glass
5030d24de9dSSimon Glassif __name__ == "__main__":
5040d24de9dSSimon Glass    import doctest
5050d24de9dSSimon Glass
5060d24de9dSSimon Glass    doctest.testmod()
507