#!/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" ] prnum_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]+)" ])) fixes_regex = re.compile( "Fixes[:]?\s+(#|https://github.com/openssl/openssl/pull/)([0-9]+)") for line in subprocess.check_output(git_command).decode().splitlines(): timestamp, branch, commit, subject = line.split(";", maxsplit=3) 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 = prnum_regex.search(message) if match: if match.group(1): prnum = match.group(1) else: prnum = match.group(2) else: prnum = "????" match = fixes_regex.search(message) if match: fixes = "#" + match.group(2) else: fixes = "" yield prnum, fixes, 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 | fixes | br | commit | subject ------- | ------ | -- | ---------- | -------------------------------------------""".format( left = left, right = right)) branch_marker = { '<': '<-', '>': '->', '=' : '==' } try: for prnum, fixes, _, branch, commit, subject in commits: print(' {:>6} | {:>6} | {} | {} | {} '.format( '#'+prnum, fixes, branch_marker[branch], commit, subject )) except subprocess.CalledProcessError as e: print(e, file=sys.stderr)