mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 01:19:28 +01:00
231 lines
7.5 KiB
Python
Executable File
231 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import math
|
|
import os.path
|
|
import sys
|
|
import subprocess
|
|
|
|
|
|
def fatal(msg):
|
|
sys.stderr.write(f"[fatal] {msg}\n")
|
|
sys.exit(1)
|
|
|
|
|
|
def verbose(options, *msg):
|
|
if options.verbose:
|
|
sys.stdout.write(f"[verbose] ")
|
|
sys.stdout.write(*msg)
|
|
sys.stdout.write('\n')
|
|
|
|
|
|
def first_line(str):
|
|
return "" if not str else str.splitlines()[0]
|
|
|
|
|
|
class Options:
|
|
def __init__(self):
|
|
ap = argparse.ArgumentParser(description="Show bugfixes differences between JBR and OpenJDK git repos",
|
|
epilog="Example: %(prog)s --jdk ./jdk11u/ --jbr ./JetBrainsRuntime/ --path src/hotspot --limit 200")
|
|
ap.add_argument('--jdk', dest='jdkpath', help='path to OpenJDK git repo', required=True)
|
|
ap.add_argument('--jbr', dest='jbrpath', help='path to JBR git repo', required=True)
|
|
ap.add_argument('--path', dest='path', help='limit to changes in this path (relative to git root)')
|
|
ap.add_argument('--limit', dest='limit', help='limit to this many log entries in --jdk repo', type=int, default=-1)
|
|
ap.add_argument('-o', dest="output_dir", help="save patches to this directory (created if necessary)")
|
|
ap.add_argument('-v', dest='verbose', help="verbose output", default=False, action='store_true')
|
|
args = ap.parse_args()
|
|
|
|
if not os.path.isdir(args.jdkpath):
|
|
fatal(f"{args.jdkpath} not a directory")
|
|
|
|
if not os.path.isdir(args.jbrpath):
|
|
fatal(f"{args.jbrpath} not a directory")
|
|
|
|
if not git_is_available():
|
|
fatal("can't run git commands; make sure git is in PATH")
|
|
|
|
self.jdkpath = args.jdkpath
|
|
self.jbrpath = args.jbrpath
|
|
self.path = args.path
|
|
self.limit = args.limit
|
|
self.output_dir = args.output_dir
|
|
self.verbose = args.verbose
|
|
|
|
|
|
class GitRepo:
|
|
def __init__(self, rootpath):
|
|
self.rootpath = rootpath
|
|
|
|
def run_git_cmd(self, git_args):
|
|
args = ["git", "-C", self.rootpath]
|
|
args.extend(git_args)
|
|
# print(f"Runnig git cmd '{' '.join(args)}'")
|
|
p = subprocess.run(args, capture_output=True, text=True)
|
|
if p.returncode != 0:
|
|
fatal(f"git returned non-zero code in {self.rootpath} ({first_line(p.stderr)})")
|
|
return p.stdout
|
|
|
|
def save_git_cmd(self, fname, git_args):
|
|
args = ["git", "-C", self.rootpath]
|
|
args.extend(git_args)
|
|
# print(f"Runnig git cmd '{' '.join(args)}'")
|
|
with open(fname, "w") as stdout_file:
|
|
p = subprocess.run(args, stdout=stdout_file)
|
|
if p.returncode != 0:
|
|
fatal(f"git returned non-zero code in {self.rootpath} ({first_line(p.stderr)})")
|
|
|
|
def current_branch(self):
|
|
branch_name = self.run_git_cmd(["branch", "--show-current"]).strip()
|
|
return branch_name
|
|
|
|
def log(self, path=None, limit=None):
|
|
cmds = ["log", "--no-decorate"]
|
|
if limit:
|
|
cmds.extend(["-n", str(limit)])
|
|
if path:
|
|
cmds.append(path)
|
|
full_log = self.run_git_cmd(cmds)
|
|
return full_log
|
|
|
|
|
|
class Commit:
|
|
def __init__(self, lines):
|
|
self.sha = lines[0].split()[1]
|
|
self.message = ""
|
|
self.bugid = None
|
|
|
|
# Commit message starts after one blank line
|
|
read_message = False
|
|
for l in lines:
|
|
if read_message:
|
|
self.message += l + "\n"
|
|
if not read_message and l == "":
|
|
read_message = True
|
|
|
|
if self.message and self.message != "" and ":" in self.message:
|
|
maybe_bugid = self.message.split(":")[0].strip()
|
|
if 10 >= len(maybe_bugid) >= 4:
|
|
self.bugid = maybe_bugid
|
|
|
|
|
|
class History:
|
|
def __init__(self, log):
|
|
log_itr = iter(log.splitlines())
|
|
self.commits = []
|
|
commit_lines = []
|
|
for line in log_itr:
|
|
if line.startswith("commit ") and len(commit_lines) > 0:
|
|
commit = Commit(commit_lines)
|
|
self.commits.append(commit)
|
|
commit_lines = []
|
|
commit_lines.append(line)
|
|
|
|
if len(commit_lines) > 0:
|
|
commit = Commit(commit_lines)
|
|
self.commits.append(commit)
|
|
|
|
def contains(self, str):
|
|
return any(str in commit.message for commit in self.commits)
|
|
|
|
def size(self):
|
|
return len(self.commits)
|
|
|
|
|
|
def print_explanation(options, jdk, jbr):
|
|
verbose(options, f"Reading history from '{jdk.rootpath}' on branch '{jdk.current_branch()}'")
|
|
if options.path:
|
|
verbose(options, f"\t(only under '{options.path}')")
|
|
verbose(options, f"Searching for same fixes in '{jbr.rootpath}' on branch '{jbr.current_branch()}'")
|
|
|
|
|
|
def git_is_available():
|
|
p = None
|
|
try:
|
|
p = subprocess.run(["git", "--help"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except:
|
|
pass
|
|
return p is not None and p.returncode == 0
|
|
|
|
|
|
def main():
|
|
check_python_min_requirements()
|
|
|
|
options = Options()
|
|
|
|
jdk = GitRepo(options.jdkpath)
|
|
jbr = GitRepo(options.jbrpath)
|
|
|
|
print_explanation(options, jdk, jbr)
|
|
|
|
commits_to_save = []
|
|
try:
|
|
jdk_log = jdk.log(options.path, options.limit)
|
|
jdk_history = History(jdk_log)
|
|
|
|
jbr_log = jbr.log(options.path)
|
|
jbr_history = History(jbr_log)
|
|
|
|
verbose(options, f"Read {jdk_history.size()} commits in JDK, {jbr_history.size()} in JBR")
|
|
|
|
for c in jdk_history.commits:
|
|
if c.bugid:
|
|
verbose(options, f"Looking for bugfix for {c.bugid}")
|
|
if not jbr_history.contains(c.bugid):
|
|
commits_to_save.append(c)
|
|
print(f"[note] Fix for {c.bugid} not found in JBR ({jbr.rootpath})")
|
|
print(f" commit {c.sha}")
|
|
print(f" {first_line(c.message).strip()}")
|
|
except KeyboardInterrupt:
|
|
fatal("Interrupted")
|
|
|
|
if len(commits_to_save) > 0 and options.output_dir:
|
|
print()
|
|
if not os.path.exists(options.output_dir):
|
|
verbose(options, f"Creating output directory {options.output_dir}")
|
|
os.makedirs(options.output_dir)
|
|
nzeroes = len(str(len(commits_to_save)))
|
|
for i, c in enumerate(reversed(commits_to_save)):
|
|
fname = os.path.join(options.output_dir, f"{str(i).zfill(nzeroes)}-{c.bugid}.patch")
|
|
print(f"[note] {c.bugid} saved as {fname}")
|
|
fname = os.path.abspath(fname)
|
|
jdk.save_git_cmd(fname, ["format-patch", "-1", c.sha, "--stdout"])
|
|
|
|
script_fname = os.path.join(options.output_dir, "apply.sh")
|
|
with open(script_fname, "w") as script_file:
|
|
print(apply_script_code.format(os.path.abspath(jbr.rootpath), os.path.abspath(options.output_dir)),
|
|
file=script_file)
|
|
print(f"[note] Execute 'bash {script_fname}' to apply patches to {jbr.rootpath}")
|
|
|
|
|
|
def check_python_min_requirements():
|
|
if sys.version_info < (3, 6):
|
|
fatal("Minimum version 3.6 is required to run this script")
|
|
|
|
|
|
apply_script_code = """
|
|
#!/bin/bash
|
|
|
|
GITROOT={0}
|
|
PATCHROOT={1}
|
|
|
|
cd $PATCHROOT || exit 1
|
|
PATCHES=$(find $PATCHROOT -name '*.patch' | sort -n)
|
|
|
|
for P in $PATCHES; do
|
|
git -C $GITROOT am $P
|
|
if [ $? != 0 ]; then
|
|
mv "$P" "$P.failed"
|
|
echo "[ERROR] Patch $P did not apply cleanly. Try applying it manually."
|
|
echo "[NOTE] Execute this script to apply the remaining patches."
|
|
exit 1
|
|
else
|
|
mv "$P" "$P.done"
|
|
fi
|
|
done
|
|
|
|
echo "[NOTE] Done applying patches; check $PATCHROOT for .patch and .patch.failed to see if all have been applied."
|
|
"""
|
|
|
|
if __name__ == '__main__':
|
|
main()
|