Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions tools/bin/mbedtls-compare-merges
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Compare merges between two TF-PSA-Crypto or Mbed TLS branches.

Invoke this script from a git checkout.
"""

import argparse
import os
import re
import subprocess
import sys
from typing import Dict, Iterator, List, NamedTuple


class MergedPR(NamedTuple):
sha: str
pull: int
description: str


Options = argparse.Namespace


def run_git(options: Options, cmd: List[str]) -> str:
"""Run git and return the text output.

Remove newline or null terminators. Assume UTF-8 encoding.
"""
raw = subprocess.check_output(['git'] + cmd,
cwd=options.directory,
encoding='utf-8')
return raw.rstrip('\0\n')


def get_base(options: Options) -> str:
"""Get the base commit (merge base of the reference branch and backport branch)."""
output = run_git(options,
['merge-base', options.backport_branch, options.reference_branch])
return output


def parse_commit_info(info: str) -> MergedPR:
sha, subject, body = info.splitlines()
m = re.match(r'Merge pull request #([0-9]+)', subject)
pull = int(m.group(1))
description = re.sub(r'(\[backport[^][]*\]|\[[0-9.]+\]:? *|backport +[0-9.]+)\W', '', body, flags=re.I).strip()
return MergedPR(sha, pull, description)

def get_merges(options: Options, base: str, head: str) -> List[MergedPR]:
"""Get pull request merged on the given branch in the given interval."""
cmd = ['log', '--merges', '--grep=^Merge pull request ']
cmd += ['--format=%h\n%s\n%b', '-z']
if options.since:
cmd.append('--since=options.since')
cmd.append(base + '..' + head)
output = run_git(options, cmd)
commits = output.split('\0')
return [parse_commit_info(info) for info in commits]


def missing_merges(dict1: Dict[str, MergedPR],
dict2: Dict[str, MergedPR]) -> Iterator[MergedPR]:
"""Find merges in the first list that are missing from the second list."""
for description, pr in dict1.items():
if description not in dict2:
yield pr

def report_missing(title: str, pr_list: List[MergedPR]) -> None:
"""Report the given pull requests.

Produce no output if the list is empty
"""
if not pr_list:
return
print(title)
for pr in sorted(pr_list, key=lambda pr: pr.pull):
print(f'#{pr.pull}\t{pr.description}')

def compare_merges(reference_list: List[MergedPR],
backport_list: List[MergedPR]) -> None:
"""Compare merges in the two list."""
reference_dict = {pr.description: pr for pr in reference_list}
backport_dict = {pr.description: pr for pr in backport_list}
not_backported = list(missing_merges(reference_dict, backport_dict))
report_missing('No backport:', not_backported)
backport_only = list(missing_merges(backport_dict, reference_dict))
report_missing('Only in LTS:', backport_only)


def main(argv: List[str]) -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--backport-branch', '-b',
metavar='BRANCH',
required=True,
help='Backport branch')
parser.add_argument('--directory', '-C',
metavar='DIR',
default=os.curdir,
help='Use this directory as the git worktree')
parser.add_argument('--reference-branch', '-a',
metavar='BRANCH',
default='development',
help='Reference branch')
parser.add_argument('--since', '-s',
metavar='DATE',
help='Only consider commits since this date')
options = parser.parse_args()
base = get_base(options)
reference = get_merges(options, base, options.reference_branch)
backport = get_merges(options, base, options.backport_branch)
compare_merges(reference, backport)

if __name__ == '__main__':
main(sys.argv[1:])