diff --git a/patchoulene/README.md b/patchoulene/README.md index 1b578ce..2a621be 100644 --- a/patchoulene/README.md +++ b/patchoulene/README.md @@ -62,6 +62,26 @@ Currently, these problems are diagnosed: The message contains content that would confuse `git mailsplit` or `git mailinfo`, which means that a patch generated from `git format-patch` on this commit would not be correctly handled by `git am`. +### `status` + +```console +$ ./patl status +``` + +Check patches in `rev` for possibly outdated patches, based on information in the database. + +An example output item is as follows: + +``` +Commit 677c9368d444 ("riscv: dts: spacemit: k3: add rfkill node for WLAN on Pico-ITX board"): + mail:20260716213314.3027969-5-aurelien@aurel32.net + has replacement: + - "riscv: dts: spacemit: k3: add rfkill node for WLAN" + mail:20260729172450.1660418-5-aurelien@aurel32.net +``` + +This shows that the commit `677c9368d444` (which is an ancestor of `rev`) is identified and, according to the database, it has a replacement. + ### `walk` ```console diff --git a/patchoulene/src/patchoulene/__init__.py b/patchoulene/src/patchoulene/__init__.py index 8ccc580..f23915e 100644 --- a/patchoulene/src/patchoulene/__init__.py +++ b/patchoulene/src/patchoulene/__init__.py @@ -1,5 +1,6 @@ import difflib import json +import os import pathlib import re import shlex @@ -213,6 +214,94 @@ def msg_merged(commit: GitCommit, primary: str) -> str: db[primary]["merged"].append(rev2) +NO_COLOR = os.getenv("NO_COLOR", "") or not sys.stdout.isatty() + + +def color_sel(color_msg: str, msg: str) -> str: + return msg if NO_COLOR else color_msg + + +def dim(msg: str) -> str: + return color_sel(f"\x1b[90m{msg}\x1b[m", msg) + + +def do_status(db: dict, commits: list[GitCommit], rev: str): + def msg_unrecorded(commit: GitCommit) -> str: + clean = clean_subject(commit.message) + + msgs = [ + f'Warning: Commit {commit.commit[:12]} ("{clean}"):', + f"- This patch has not been recorded, please run './patl walk {rev}'", + "", + ] + + return "\n".join(msgs) + + def msg_replacement_header(commit: GitCommit) -> str: + clean = clean_subject(commit.message) + guessed = guess_id(commit.message) + primary = guessed[0] + + msgs = [ + f'Commit {commit.commit[:12]} ("{clean}"):', + f" {dim(primary)}", + f" has replacement:", + ] + + return "\n".join(msgs) + + def msg_replacement( + commit: GitCommit, pid: str, subject: str | None, merged: list[str] + ) -> str: + clean = clean_subject(commit.message) + subject_suffix = "" + if subject == clean: + subject_suffix = " (identical subject)" + suffix = "" + if merged := info.get("merged", []): + suffix = f" (merged {', '.join(merged)})" + msgs = [f' - "{subject}"{subject_suffix}', f" {dim(pid)}{suffix}"] + return "\n".join(msgs) + + for c in commits: + guessed = guess_id(c.message) + if not guessed: + continue + primary = guessed[0] + + if primary not in db: + 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) + + if replacements != {primary}: + print(msg_replacement_header(c)) + for pid in replacements: + info = db.get(pid, {}) + subject: str = info.get("subject", None) + merged: list[str] = info.get("merged", []) + + print(msg_replacement(c, pid, subject, merged)) + print() + + def main(): repo = GitRepo(".") match sys.argv[1:]: @@ -220,6 +309,12 @@ def main(): base = guess_base(repo, ref) print(f"Base for {ref} is {base}", file=sys.stderr) do_check_messages(repo, [f"^refs/tags/{base}", ref]) + case ["status", rev]: + base = guess_base(repo, rev) + print(f"Base for {rev} is {base}", file=sys.stderr) + commits = repo.commit_list([f"^refs/tags/{base}", rev]) + db = read_patch_db() + do_status(db, commits, rev) case ["diff", rev1, rev2]: base1 = guess_base(repo, rev1) print(f"Base for {rev1} is {base1}", file=sys.stderr)