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