diff options
Diffstat (limited to 'bin/commit_in_branch.py')
-rwxr-xr-x | bin/commit_in_branch.py | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/bin/commit_in_branch.py b/bin/commit_in_branch.py new file mode 100755 index 00000000000..e4e2edb50ab --- /dev/null +++ b/bin/commit_in_branch.py @@ -0,0 +1,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: str, 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: str, 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 + """ + out = subprocess.check_output(['git', 'log', '--format=%H', + branch + '-branchpoint..' + 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('/') + 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() + + (upstream, branch) = args.branch.split('/') + + if branch_has_commit(upstream, branch, args.commit): + print_(args, True, 'Commit ' + args.commit + ' is in branch ' + branch) + exit(0) + + backport = branch_has_backport_of_commit(upstream, branch, args.commit) + if backport: + print_(args, True, + 'Commit ' + args.commit + ' was backported to branch ' + branch + ' as commit ' + backport) + exit(0) + + print_(args, False, 'Commit ' + args.commit + ' is NOT in branch ' + branch) + exit(1) |