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