mirror of OpenBSD xenocara tree
github.com/openbsd/xenocara
openbsd
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_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_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 upstream, _ = upstream_branch.split('/', 1)
65
66 out = subprocess.check_output(['git', 'log', '--format=%H',
67 upstream + '..' + upstream_branch,
68 '--grep', 'cherry picked from commit ' + commit],
69 stderr=subprocess.DEVNULL)
70 return out.decode().strip()
71
72
73def canonicalize_commit(commit: str) -> str:
74 """
75 Takes a commit-ish and returns a commit sha1 if the commit exists
76 """
77
78 # Make sure input is valid first
79 if not is_commit_valid(commit):
80 raise argparse.ArgumentTypeError('invalid commit identifier: ' + commit)
81
82 out = subprocess.check_output(['git', 'rev-parse', commit],
83 stderr=subprocess.DEVNULL)
84 return out.decode().strip()
85
86
87def validate_branch(branch: str) -> str:
88 if '/' not in branch:
89 raise argparse.ArgumentTypeError('must be in the form `remote/branch`')
90
91 out = subprocess.check_output(['git', 'remote', '--verbose'],
92 stderr=subprocess.DEVNULL)
93 remotes = out.decode().splitlines()
94 upstream, _ = branch.split('/', 1)
95 valid_remote = False
96 for line in remotes:
97 if line.startswith(upstream + '\t'):
98 valid_remote = True
99
100 if not valid_remote:
101 raise argparse.ArgumentTypeError('Invalid remote: ' + upstream)
102
103 if not is_commit_valid(branch):
104 raise argparse.ArgumentTypeError('Invalid branch: ' + branch)
105
106 return branch
107
108
109if __name__ == "__main__":
110 parser = argparse.ArgumentParser(description="""
111 Returns 0 if the commit is present in the branch,
112 1 if it's not,
113 and 2 if it couldn't be determined (eg. invalid commit)
114 """)
115 parser.add_argument('commit',
116 type=canonicalize_commit,
117 help='commit sha1')
118 parser.add_argument('branch',
119 type=validate_branch,
120 help='branch to check, in the form `remote/branch`')
121 parser.add_argument('--quiet',
122 action='store_true',
123 help='suppress all output; exit code can still be used')
124 parser.add_argument('--color',
125 choices=['auto', 'always', 'never'],
126 default='auto',
127 help='colorize output (default: true if stdout is a terminal)')
128 args = parser.parse_args()
129
130 if branch_has_commit(args.branch, args.commit):
131 print_(args, True, 'Commit ' + args.commit + ' is in branch ' + args.branch)
132 exit(0)
133
134 backport = branch_has_backport_of_commit(args.branch, args.commit)
135 if backport:
136 print_(args, True,
137 'Commit ' + args.commit + ' was backported to branch ' + args.branch + ' as commit ' + backport)
138 exit(0)
139
140 print_(args, False, 'Commit ' + args.commit + ' is NOT in branch ' + args.branch)
141 exit(1)