• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3import argparse
4import subprocess
5import sys
6
7
8def print_(args: argparse.Namespace, success: bool, message: str) -> None:
9    """
10    Print function with extra coloring when supported and/or requested,
11    and with a "quiet" switch
12    """
13
14    COLOR_SUCCESS = '\033[32m'
15    COLOR_FAILURE = '\033[31m'
16    COLOR_RESET = '\033[0m'
17
18    if args.quiet:
19        return
20
21    if args.color == 'auto':
22        use_colors = sys.stdout.isatty()
23    else:
24        use_colors = args.color == 'always'
25
26    s = ''
27    if use_colors:
28        if success:
29            s += COLOR_SUCCESS
30        else:
31            s += COLOR_FAILURE
32
33    s += message
34
35    if use_colors:
36        s += COLOR_RESET
37
38    print(s)
39
40
41def is_commit_valid(commit: str) -> bool:
42    ret = subprocess.call(['git', 'cat-file', '-e', commit],
43                          stdout=subprocess.DEVNULL,
44                          stderr=subprocess.DEVNULL)
45    return ret == 0
46
47
48def branch_has_commit(upstream: str, branch: str, commit: str) -> bool:
49    """
50    Returns True if the commit is actually present in the branch
51    """
52    ret = subprocess.call(['git', 'merge-base', '--is-ancestor',
53                           commit, upstream + '/' + branch],
54                          stdout=subprocess.DEVNULL,
55                          stderr=subprocess.DEVNULL)
56    return ret == 0
57
58
59def branch_has_backport_of_commit(upstream: str, branch: str, commit: str) -> str:
60    """
61    Returns the commit hash if the commit has been backported to the branch,
62    or an empty string if is hasn't
63    """
64    out = subprocess.check_output(['git', 'log', '--format=%H',
65                                   branch + '-branchpoint..' + upstream + '/' + branch,
66                                   '--grep', 'cherry picked from commit ' + commit],
67                                  stderr=subprocess.DEVNULL)
68    return out.decode().strip()
69
70
71def canonicalize_commit(commit: str) -> str:
72    """
73    Takes a commit-ish and returns a commit sha1 if the commit exists
74    """
75
76    # Make sure input is valid first
77    if not is_commit_valid(commit):
78        raise argparse.ArgumentTypeError('invalid commit identifier: ' + commit)
79
80    out = subprocess.check_output(['git', 'rev-parse', commit],
81                                  stderr=subprocess.DEVNULL)
82    return out.decode().strip()
83
84
85def validate_branch(branch: str) -> str:
86    if '/' not in branch:
87        raise argparse.ArgumentTypeError('must be in the form `remote/branch`')
88
89    out = subprocess.check_output(['git', 'remote', '--verbose'],
90                                  stderr=subprocess.DEVNULL)
91    remotes = out.decode().splitlines()
92    (upstream, _) = branch.split('/')
93    valid_remote = False
94    for line in remotes:
95        if line.startswith(upstream + '\t'):
96            valid_remote = True
97
98    if not valid_remote:
99        raise argparse.ArgumentTypeError('Invalid remote: ' + upstream)
100
101    if not is_commit_valid(branch):
102        raise argparse.ArgumentTypeError('Invalid branch: ' + branch)
103
104    return branch
105
106
107if __name__ == "__main__":
108    parser = argparse.ArgumentParser(description="""
109    Returns 0 if the commit is present in the branch,
110    1 if it's not,
111    and 2 if it couldn't be determined (eg. invalid commit)
112    """)
113    parser.add_argument('commit',
114                        type=canonicalize_commit,
115                        help='commit sha1')
116    parser.add_argument('branch',
117                        type=validate_branch,
118                        help='branch to check, in the form `remote/branch`')
119    parser.add_argument('--quiet',
120                        action='store_true',
121                        help='suppress all output; exit code can still be used')
122    parser.add_argument('--color',
123                        choices=['auto', 'always', 'never'],
124                        default='auto',
125                        help='colorize output (default: true if stdout is a terminal)')
126    args = parser.parse_args()
127
128    (upstream, branch) = args.branch.split('/')
129
130    if branch_has_commit(upstream, branch, args.commit):
131        print_(args, True, 'Commit ' + args.commit + ' is in branch ' + branch)
132        exit(0)
133
134    backport = branch_has_backport_of_commit(upstream, branch, args.commit)
135    if backport:
136        print_(args, True,
137               'Commit ' + args.commit + ' was backported to branch ' + branch + ' as commit ' + backport)
138        exit(0)
139
140    print_(args, False, 'Commit ' + args.commit + ' is NOT in branch ' + branch)
141    exit(1)
142