From d6fa48f9932dbb67ab2dfb4fce50428a7d16e267 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 18 Aug 2026 13:50:40 +0800 Subject: [PATCH 1/2] tools: patchoulene: record: Extract helper db_by_subject() Extract a helper function to create a patch index by subject, for future use. Signed-off-by: Vivian Wang --- patchoulene/src/patchoulene/__init__.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/patchoulene/src/patchoulene/__init__.py b/patchoulene/src/patchoulene/__init__.py index f486246..88979bc 100644 --- a/patchoulene/src/patchoulene/__init__.py +++ b/patchoulene/src/patchoulene/__init__.py @@ -110,6 +110,19 @@ def prompt_yn(default: bool = False): return False +def db_by_subject(db: dict) -> dict[str, list[str]]: + by_subject = {} + + for pid, data in db.items(): + if "subject" not in data: + continue + if data["subject"] not in by_subject: + by_subject[data["subject"]] = [] + by_subject[data["subject"]].append(pid) + + return by_subject + + def do_record(repo: GitRepo, db: dict, rev1: str, rev2: str): def msg_possible_match_header(commit: GitCommit, primary: str) -> str: clean = clean_subject(commit.message) @@ -146,14 +159,7 @@ def msg_merged(commit: GitCommit, primary: str) -> str: is_mainline = parse_base(rev2)[2] == 0 - by_subject = {} - - for pid, data in db.items(): - if "subject" not in data: - continue - if data["subject"] not in by_subject: - by_subject[data["subject"]] = [] - by_subject[data["subject"]].append(pid) + by_subject = db_by_subject(db) for c in repo.commit_list([f"^{rev1}", rev2]): upstream = [] From 01a60ae124937913612b98de19ffae78de5e0269 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 18 Aug 2026 13:50:40 +0800 Subject: [PATCH 2/2] tools: patchoulene: walk: Find subject matches Add support for finding subject matches in walk. As before, subject-only matches are marked as "weak match". Signed-off-by: Vivian Wang --- patchoulene/src/patchoulene/__init__.py | 86 +++++++++++++++---------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/patchoulene/src/patchoulene/__init__.py b/patchoulene/src/patchoulene/__init__.py index 88979bc..dde7943 100644 --- a/patchoulene/src/patchoulene/__init__.py +++ b/patchoulene/src/patchoulene/__init__.py @@ -232,6 +232,26 @@ def dim(msg: str) -> str: return color_sel(f"\x1b[90m{msg}\x1b[m", msg) +def walk_replacements(db: dict, pid: str) -> set[str]: + replacements = set() + todo = {pid} + + while todo: + pid = todo.pop() + + if pid not in db or not db[pid].get("replacement", None): + replacements.add(pid) + continue + + repl = db[pid]["replacement"] + if not isinstance(repl, list): + repl = [repl] + + todo.update(repl) + + return replacements + + def do_status(db: dict, commits: list[GitCommit], rev: str): def msg_unrecorded(commit: GitCommit) -> str: clean = clean_subject(commit.message) @@ -280,23 +300,7 @@ def msg_replacement( print(msg_unrecorded(c), file=sys.stderr) continue - # Walk the replacment graph to find the set of patches that replaces c - - replacements = set() - todo = {primary} - - while todo: - pid = todo.pop() - - if pid not in db or not db[pid].get("replacement", None): - replacements.add(pid) - continue - - repl = db[pid]["replacement"] - if not isinstance(repl, list): - repl = [repl] - - todo.update(repl) + replacements = walk_replacements(db, primary) if replacements != {primary}: print(msg_replacement_header(c)) @@ -733,35 +737,47 @@ def main(): base = guess_base(repo, ref) print(f"Base for {ref} is {base}", file=sys.stderr) db = read_patch_db() + by_subject = db_by_subject(db) + for c in repo.commit_list([f"^refs/tags/{base}", ref]): guess = guess_id(c.message) if not guess: continue primary = guess[0] + replacements = walk_replacements(db, primary) + clean = clean_subject(c.message) if primary not in db: db[primary] = { "subject": clean, } - for secondary in guess[1:]: - if secondary in db: - if db[secondary].get("replacement", None): - continue - old_subject = db[secondary].get("subject", "(Unknown)") - is_identical = ( - " (identical subject)" if old_subject == clean else "" - ) - print(f"Is new patch {primary}", file=sys.stderr) - print(f' "{clean}"', file=sys.stderr) - print( - f"... the replacement of patch {secondary}?", - file=sys.stderr, - ) - print(f" (Identifier matches)", file=sys.stderr) - print(f' "{old_subject}"{is_identical}', file=sys.stderr) - if prompt_yn(): - db[secondary]["replacement"] = primary + id_matches = set(pid for pid in guess[1:] if pid in db) + possible_matches = id_matches | set(by_subject.get(clean, [])) + possible_matches -= {primary} + + possible_matches = { + pid + for pid in possible_matches + if pid in db + and pid not in replacements + and not db[pid].get("replacement", None) + } + + for possible in possible_matches: + old_subject = db[possible].get("subject", "(Unknown)") + is_identical = ( + " (identical subject)" if old_subject == clean else "" + ) + print(f"Is new patch {primary}", file=sys.stderr) + print(f' "{clean}"', file=sys.stderr) + print(f"... the replacement of patch {possible}?", file=sys.stderr) + if possible not in id_matches: + print(f"* (weak match)", file=sys.stderr) + print(f' "{old_subject}"{is_identical}', file=sys.stderr) + if prompt_yn(): + db[possible]["replacement"] = primary + write_patch_db(db) case ["record", rev1, rev2]: