cherry-checker: initial commit
authorDr. Matthias St. Pierre <Matthias.St.Pierre@ncp-e.com>
Sun, 14 Oct 2018 12:05:09 +0000 (14:05 +0200)
committerRichard Levitte <levitte@openssl.org>
Tue, 23 Oct 2018 16:15:14 +0000 (18:15 +0200)
usage: cherry-checker [-h] [-a] [-s] [-r]

Shows the commits in 'master...OpenSSL_1_1_1-stable' which are eligible for
cherry-picking. A commit is considered cherry-picked, if there is another
commit on the "other side" which introduces an equivalent patch. For details,
see the documentation of the '--cherry-mark' option in the git-log(1) manpage.

optional arguments:
  -h, --help    show this help message and exit
  -a, --all     Show all commits, also those which have been cherry-picked.
  -s, --sort    Sort commits w.r.t. pull request number and author date.
  -r, --remote  Compare the remote branches instead of the local ones.

Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/tools/pull/32)

review-tools/cherry-checker [new file with mode: 0755]

diff --git a/review-tools/cherry-checker b/review-tools/cherry-checker
new file mode 100755 (executable)
index 0000000..d65e801
--- /dev/null
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+
+import argparse
+import subprocess
+import re
+import sys
+
+left  = "master"
+right = "OpenSSL_1_1_1-stable"
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(
+        description = """Shows the commits in '{left}...{right}'
+        which are eligible for cherry-picking. A commit is considered
+        cherry-picked, if there is another commit on the "other side"
+        which introduces an equivalent patch.
+        For details, see the documentation of the '--cherry-mark' option
+        in the git-log(1) manpage.
+        """.format(left=left, right=right))
+
+    parser.add_argument(
+        '-a', '--all',
+        action = 'store_true',
+        help = "Show all commits, also those which have been cherry-picked."\
+        )
+
+    parser.add_argument(
+        '-s', '--sort',
+        action = 'store_true',
+        help = "Sort commits w.r.t. pull request number and author date."\
+        )
+
+    parser.add_argument(
+        '-r', '--remote',
+        action = 'store_true',
+        help = "Compare the remote branches instead of the local ones."\
+        )
+
+    args = parser.parse_args()
+
+    return args
+
+
+def check_openssl_git_repo():
+    """Checks whether we're inside a openssl.git downstrem repository"""
+    try:
+        if "/openssl.git" in subprocess.check_output(
+                ["git", "remote", "-v"]
+        ).decode():
+            return True;
+    except:
+        pass
+
+    return False
+
+
+def get_remote():
+    try:
+        return subprocess.check_output(
+            ["git", "config", "branch.master.remote"]
+            ).decode().trim()
+    except:
+        return "origin"
+
+
+def pick_cherries(left, right, all = False):
+    """Lists all commits from the symmetric difference of left and right
+
+    By default, all commits are omitted which have an 'equivalent' commit
+    on the other side, unless 'all' == True.
+    """
+
+    git_command = [
+        "git", "log", "--oneline", "--cherry-mark", "--left-right",
+        left + "..." + right, "--pretty=%at;%m;%h;%s"
+    ]
+
+    regex   = re.compile("|".join([
+        # The standard pull request annotation
+        "\(Merged from https://github.com/openssl/openssl/pull/([0-9]+)\)",
+        # @kroeck's special pull request annotation ;-)
+        "GH: #([0-9]+)"
+    ]))
+
+    for line in subprocess.check_output(git_command).decode().splitlines():
+
+        timestamp, branch, commit, subject = line.split(";")
+
+        if branch == '=' and not all:
+            continue
+
+        # shorten overlong subject lines
+        if len(subject) > 70:
+            subject = subject[:70] + "..."
+
+        # search commit message for pull request number
+        message = subprocess.check_output(
+            ["git", "show", "--no-patch", commit]
+        ).decode()
+
+        match = regex.search(message)
+        if match:
+            if match.group(1):
+                prnum = match.group(1)
+            else:
+                prnum = match.group(2)
+        else:
+            prnum = "????"
+
+        yield prnum, timestamp, branch, commit, subject
+
+
+
+if __name__ == '__main__':
+    args = parse_arguments()
+
+    if not check_openssl_git_repo():
+        print("cherry-checker: Not inside an openssl git repository.", file=sys.stderr)
+        sys.exit(1)
+
+
+    if args.remote:
+        remote = get_remote()
+        left  = remote+"/"+left
+        right = remote+"/"+right
+
+    commits = pick_cherries(left, right, args.all)
+
+    if args.sort:
+        commits = sorted(commits, reverse=True)
+
+    print("""These cherries are hanging on the git-tree:
+
+  <-  {left}
+  ->  {right}
+  ==  both
+
+ prnum | br |   commit   |   subject
+ ----- | -- | ---------- | -------------------------------------------""".format(
+         left = left,
+         right = right))
+
+    branch_marker = { '<': '<-', '>': '->', '=' : '==' }
+
+    try:
+        for prnum, _, branch, commit, subject in commits:
+            print(' #{:>4} | {} | {} | {} '.format(
+                prnum, branch_marker[branch], commit, subject
+            ))
+    except subprocess.CalledProcessError as e:
+        print(e, file=sys.stderr)