pick-to-branch: Improve fix of behavior on failed cherry-pick
[tools.git] / review-tools / cherry-checker
1 #!/usr/bin/env python3
2
3 import argparse
4 import subprocess
5 import re
6 import sys
7
8 left  = "master"
9 right = "OpenSSL_1_1_1-stable"
10
11
12 def parse_arguments():
13     parser = argparse.ArgumentParser(
14         description = """Shows the commits in '{left}...{right}'
15         which are eligible for cherry-picking. A commit is considered
16         cherry-picked, if there is another commit on the "other side"
17         which introduces an equivalent patch.
18         For details, see the documentation of the '--cherry-mark' option
19         in the git-log(1) manpage.
20         """.format(left=left, right=right))
21
22     parser.add_argument(
23         '-a', '--all',
24         action = 'store_true',
25         help = "Show all commits, also those which have been cherry-picked."\
26         )
27
28     parser.add_argument(
29         '-s', '--sort',
30         action = 'store_true',
31         help = "Sort commits w.r.t. pull request number and author date."\
32         )
33
34     parser.add_argument(
35         '-r', '--remote',
36         action = 'store_true',
37         help = "Compare the remote branches instead of the local ones."\
38         )
39
40     args = parser.parse_args()
41
42     return args
43
44
45 def check_openssl_git_repo():
46     """Checks whether we're inside a openssl.git downstrem repository"""
47     try:
48         if "/openssl.git" in subprocess.check_output(
49                 ["git", "remote", "-v"]
50         ).decode():
51             return True;
52     except:
53         pass
54
55     return False
56
57
58 def get_remote():
59     try:
60         return subprocess.check_output(
61             ["git", "config", "branch.master.remote"]
62             ).decode().trim()
63     except:
64         return "origin"
65
66
67 def pick_cherries(left, right, all = False):
68     """Lists all commits from the symmetric difference of left and right
69
70     By default, all commits are omitted which have an 'equivalent' commit
71     on the other side, unless 'all' == True.
72     """
73
74     git_command = [
75         "git", "log", "--oneline", "--cherry-mark", "--left-right",
76         left + "..." + right, "--pretty=%at;%m;%h;%s"
77     ]
78
79     prnum_regex   = re.compile("|".join([
80         # The standard pull request annotation
81         "\(Merged from https://github.com/openssl/openssl/pull/([0-9]+)\)",
82         # @kroeck's special pull request annotation ;-)
83         "GH: #([0-9]+)"
84     ]))
85
86     fixes_regex   = re.compile(
87         "Fixes[:]?\s+(#|https://github.com/openssl/openssl/pull/)([0-9]+)")
88
89     for line in subprocess.check_output(git_command).decode().splitlines():
90
91         timestamp, branch, commit, subject = line.split(";", maxsplit=3)
92
93         if branch == '=' and not all:
94             continue
95
96         # shorten overlong subject lines
97         if len(subject) > 70:
98             subject = subject[:70] + "..."
99
100         # search commit message for pull request number
101         message = subprocess.check_output(
102             ["git", "show", "--no-patch", commit]
103         ).decode()
104
105         match = prnum_regex.search(message)
106         if match:
107             if match.group(1):
108                 prnum = match.group(1)
109             else:
110                 prnum = match.group(2)
111         else:
112             prnum = "????"
113
114         match = fixes_regex.search(message)
115         if match:
116             fixes = "#" + match.group(2)
117         else:
118             fixes = ""
119
120         yield prnum, fixes, timestamp, branch, commit, subject
121
122
123
124 if __name__ == '__main__':
125     args = parse_arguments()
126
127     if not check_openssl_git_repo():
128         print("cherry-checker: Not inside an openssl git repository.", file=sys.stderr)
129         sys.exit(1)
130
131
132     if args.remote:
133         remote = get_remote()
134         left  = remote+"/"+left
135         right = remote+"/"+right
136
137     commits = pick_cherries(left, right, args.all)
138
139     if args.sort:
140         commits = sorted(commits, reverse=True)
141
142     print("""These cherries are hanging on the git-tree:
143
144   <-  {left}
145   ->  {right}
146   ==  both
147
148  prnum  | fixes  | br |   commit   |   subject
149 ------- | ------ | -- | ---------- | -------------------------------------------""".format(
150          left = left,
151          right = right))
152
153     branch_marker = { '<': '<-', '>': '->', '=' : '==' }
154
155     try:
156         for prnum, fixes, _, branch, commit, subject in commits:
157             print(' {:>6} | {:>6} | {} | {} | {} '.format(
158                 '#'+prnum, fixes, branch_marker[branch], commit, subject
159             ))
160     except subprocess.CalledProcessError as e:
161         print(e, file=sys.stderr)