mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-10-14 12:05:03 +00:00
contrib: add "find-backports" script
This script was previously on the "automation" branch. Add it to "master".
This commit is contained in:
parent
317171ed6e
commit
57cfa5daf9
334
contrib/scripts/find-backports
Executable file
334
contrib/scripts/find-backports
Executable file
|
@ -0,0 +1,334 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import pprint
|
||||
|
||||
|
||||
FNULL = open(os.devnull, "w")
|
||||
pp = pprint.PrettyPrinter(indent=4)
|
||||
|
||||
|
||||
def print_err(s):
|
||||
print(s, file=sys.stderr)
|
||||
|
||||
|
||||
def die(s):
|
||||
print_err(s)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def memoize(f):
|
||||
memo = {}
|
||||
|
||||
def helper(x):
|
||||
if x not in memo:
|
||||
memo[x] = f(x)
|
||||
return memo[x]
|
||||
|
||||
return helper
|
||||
|
||||
|
||||
def re_bin(r):
|
||||
return r.encode("utf8")
|
||||
|
||||
|
||||
def _keys_to_dict(itr):
|
||||
d = collections.OrderedDict()
|
||||
for c in itr:
|
||||
d[c] = None
|
||||
return d
|
||||
|
||||
|
||||
@memoize
|
||||
def git_ref_exists(ref):
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
["git", "rev-parse", "--verify", str(ref) + "^{commit}"], stderr=FNULL
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
try:
|
||||
o = out.decode("ascii").strip()
|
||||
if len(o) == 40:
|
||||
return o
|
||||
except Exception:
|
||||
pass
|
||||
raise Exception("git-rev-parse for '%s' returned unexpected output %s" % (ref, out))
|
||||
|
||||
|
||||
@memoize
|
||||
def git_get_head_name(ref):
|
||||
out = subprocess.check_output(
|
||||
["git", "rev-parse", "--symbolic-full-name", str(ref)], stderr=FNULL
|
||||
)
|
||||
return out.decode("utf-8").strip()
|
||||
|
||||
|
||||
def git_merge_base(a, b):
|
||||
out = subprocess.check_output(["git", "merge-base", str(a), str(b)], stderr=FNULL)
|
||||
out = out.decode("ascii").strip()
|
||||
assert git_ref_exists(out)
|
||||
return out
|
||||
|
||||
|
||||
def git_all_commits_grep(rnge, grep=None):
|
||||
if grep:
|
||||
grep = ["--grep=%s" % (str(grep))]
|
||||
notes = ["-c", "notes.displayref=refs/notes/bugs"]
|
||||
else:
|
||||
grep = []
|
||||
notes = []
|
||||
out = subprocess.check_output(
|
||||
["git"]
|
||||
+ notes
|
||||
+ ["log", "--pretty=%H", "--notes", "--reverse"]
|
||||
+ grep
|
||||
+ [str(rnge)],
|
||||
stderr=FNULL,
|
||||
)
|
||||
return list([x for x in out.decode("ascii").split("\n") if x])
|
||||
|
||||
|
||||
def git_logg(commits):
|
||||
commits = list(commits)
|
||||
if not commits:
|
||||
return ""
|
||||
out = subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"log",
|
||||
"--no-show-signature",
|
||||
"--no-walk",
|
||||
"--pretty=format:%Cred%h%Creset - %Cgreen(%ci)%Creset [%C(yellow)%an%Creset] %s%C(yellow)%d%Creset",
|
||||
"--abbrev-commit",
|
||||
"--date=local",
|
||||
]
|
||||
+ list([str(c) for c in commits]),
|
||||
stderr=FNULL,
|
||||
)
|
||||
return out.decode("utf-8").strip()
|
||||
|
||||
|
||||
@memoize
|
||||
def git_all_commits(rnge):
|
||||
return git_all_commits_grep(rnge)
|
||||
|
||||
|
||||
def git_commit_sorted(commits):
|
||||
commits = list(commits)
|
||||
if not commits:
|
||||
return []
|
||||
out = subprocess.check_output(
|
||||
["git", "log", "--no-walk", "--pretty=%H", "--reverse"]
|
||||
+ list([str(x) for x in commits]),
|
||||
stderr=FNULL,
|
||||
)
|
||||
out = out.decode("ascii")
|
||||
return list([x for x in out.split("\n") if x])
|
||||
|
||||
|
||||
@memoize
|
||||
def git_ref_commit_body(ref):
|
||||
return subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"-c",
|
||||
"notes.displayref=refs/notes/bugs",
|
||||
"log",
|
||||
"-n1",
|
||||
"--pretty=%B%n%N",
|
||||
str(ref),
|
||||
],
|
||||
stderr=FNULL,
|
||||
)
|
||||
|
||||
|
||||
@memoize
|
||||
def git_ref_commit_body_get_fixes(ref):
|
||||
body = git_ref_commit_body(ref)
|
||||
result = []
|
||||
for mo in re.finditer(re_bin("[fF]ixes: *([0-9a-z]+).*"), body):
|
||||
c = mo.group(1).decode("ascii")
|
||||
h = git_ref_exists(c)
|
||||
if h:
|
||||
result.append(h)
|
||||
return result
|
||||
|
||||
|
||||
@memoize
|
||||
def git_ref_commit_body_get_cherry_picked_one(ref):
|
||||
ref = git_ref_exists(ref)
|
||||
if not ref:
|
||||
return None
|
||||
body = git_ref_commit_body(ref)
|
||||
result = None
|
||||
for mo in re.finditer(
|
||||
re_bin(".*\(cherry picked from commit ([0-9a-z]+)\).*"), body
|
||||
):
|
||||
c = mo.group(1).decode("ascii")
|
||||
h = git_ref_exists(c)
|
||||
if h:
|
||||
if not result:
|
||||
result = [h]
|
||||
else:
|
||||
result.append(h)
|
||||
return result
|
||||
|
||||
|
||||
@memoize
|
||||
def git_ref_commit_body_get_cherry_picked_recurse(ref):
|
||||
ref = git_ref_exists(ref)
|
||||
if not ref:
|
||||
return None
|
||||
|
||||
def do_recurse(result, ref):
|
||||
result2 = git_ref_commit_body_get_cherry_picked_one(ref)
|
||||
if result2:
|
||||
extra = list([h2 for h2 in result2 if h2 not in result])
|
||||
if extra:
|
||||
result.extend(extra)
|
||||
for h2 in extra:
|
||||
do_recurse(result, h2)
|
||||
|
||||
result = []
|
||||
do_recurse(result, ref)
|
||||
return result
|
||||
|
||||
|
||||
def git_commits_annotate_fixes(rnge):
|
||||
commits = git_all_commits(rnge)
|
||||
c_dict = _keys_to_dict(commits)
|
||||
for c in git_all_commits_grep(rnge, grep="[Ff]ixes:"):
|
||||
ff = git_ref_commit_body_get_fixes(c)
|
||||
if ff:
|
||||
c_dict[c] = ff
|
||||
return c_dict
|
||||
|
||||
|
||||
def git_commits_annotate_cherry_picked(rnge):
|
||||
commits = git_all_commits(rnge)
|
||||
c_dict = _keys_to_dict(commits)
|
||||
for c in git_all_commits_grep(ref_head, grep="cherry picked from commit"):
|
||||
ff = git_ref_commit_body_get_cherry_picked_recurse(c)
|
||||
if ff:
|
||||
c_dict[c] = ff
|
||||
return c_dict
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) <= 1:
|
||||
ref_head0 = "HEAD"
|
||||
else:
|
||||
ref_head0 = sys.argv[1]
|
||||
|
||||
ref_head = git_ref_exists(ref_head0)
|
||||
if not ref_head:
|
||||
die('Ref "%s" does not exist' % (ref_head0))
|
||||
|
||||
ref_upstreams = []
|
||||
if len(sys.argv) <= 2:
|
||||
head_name = git_get_head_name(ref_head0)
|
||||
match = False
|
||||
if head_name:
|
||||
match = re.match("^refs/(heads|remotes/[^/]*)/nm-1-([0-9]+)$", head_name)
|
||||
if match:
|
||||
i = int(match.group(2))
|
||||
while True:
|
||||
i += 2
|
||||
r = "nm-1-" + str(i)
|
||||
if not git_ref_exists(r):
|
||||
r = "refs/remotes/origin/nm-1-" + str(i)
|
||||
if not git_ref_exists(r):
|
||||
break
|
||||
ref_upstreams.append(r)
|
||||
ref_upstreams.append("master")
|
||||
|
||||
if not ref_upstreams:
|
||||
if len(sys.argv) <= 2:
|
||||
ref_upstreams = ["master"]
|
||||
else:
|
||||
ref_upstreams = list(sys.argv[2:])
|
||||
|
||||
for h in ref_upstreams:
|
||||
if not git_ref_exists(h):
|
||||
die('Upstream ref "%s" does not exist' % (h))
|
||||
|
||||
print_err("Check %s (%s)" % (ref_head0, ref_head))
|
||||
print_err("Upstream refs: %s" % (ref_upstreams))
|
||||
|
||||
print_err('Check patches of "%s"...' % (ref_head))
|
||||
own_commits_list = git_all_commits(ref_head)
|
||||
own_commits_cherry_picked = git_commits_annotate_cherry_picked(ref_head)
|
||||
|
||||
cherry_picks_all = collections.OrderedDict()
|
||||
for c, cherry_picked in own_commits_cherry_picked.items():
|
||||
if cherry_picked:
|
||||
for c2 in cherry_picked:
|
||||
l = cherry_picks_all.get(c2)
|
||||
if not l:
|
||||
cherry_picks_all[c2] = [c]
|
||||
else:
|
||||
l.append(c)
|
||||
|
||||
own_commits_cherry_picked_flat = set()
|
||||
for c, p in own_commits_cherry_picked.items():
|
||||
own_commits_cherry_picked_flat.add(c)
|
||||
if p:
|
||||
own_commits_cherry_picked_flat.update(p)
|
||||
|
||||
# print(">>> own_commits_cherry_picked")
|
||||
# pp.pprint(own_commits_cherry_picked)
|
||||
|
||||
# print(">>> cherry_picks_all")
|
||||
# pp.pprint(cherry_picks_all)
|
||||
|
||||
# find all commits on the upstream branches that fix another commit.
|
||||
fixing_commits = {}
|
||||
for ref_upstream in ref_upstreams:
|
||||
ref_str = ref_head + ".." + ref_upstream
|
||||
print_err('Check upstream patches "%s"...' % (ref_str))
|
||||
for c, fixes in git_commits_annotate_fixes(ref_str).items():
|
||||
# print(">>> test %s ==> %s" % (c, fixes))
|
||||
if not fixes:
|
||||
# print(">>> test %s ==> SKIP" % (c))
|
||||
continue
|
||||
if c in cherry_picks_all:
|
||||
# commit 'c' is already backported. Skip it.
|
||||
# print(">>> in cherry_picks_all")
|
||||
continue
|
||||
for f in fixes:
|
||||
if f not in own_commits_cherry_picked_flat:
|
||||
# commit "c" fixes commit "f", but this is not one of our own commits
|
||||
# and not interesting.
|
||||
# print(">>> fixes %s not in own_commits_cherry_picked" % (f))
|
||||
continue
|
||||
# print(">>> take %s (fixes %s)" % (c, fixes))
|
||||
fixing_commits[c] = fixes
|
||||
break
|
||||
|
||||
extra = collections.OrderedDict(
|
||||
[(c, git_ref_commit_body_get_cherry_picked_recurse(c)) for c in fixing_commits]
|
||||
)
|
||||
extra2 = []
|
||||
for c in extra:
|
||||
is_back = False
|
||||
for e_v in extra.values():
|
||||
if c in e_v:
|
||||
is_back = True
|
||||
break
|
||||
if not is_back:
|
||||
extra2.append(c)
|
||||
|
||||
commits_good = list(fixing_commits)
|
||||
commits_good = extra2
|
||||
|
||||
commits_good = git_commit_sorted(commits_good)
|
||||
|
||||
print_err(git_logg(commits_good))
|
||||
|
||||
for c in reversed(commits_good):
|
||||
print("%s" % (c))
|
Loading…
Reference in a new issue