From 636343986b37cee2257fe5f1c3935d834a29754b Mon Sep 17 00:00:00 2001 From: Priya Sundaram Date: Tue, 15 Sep 2026 13:10:08 -0500 Subject: [PATCH 1/2] Move DIRECTORY.md to its own section in pr_file_map.py DIRECTORY.md is auto-generated and touched by nearly every open PR, so it dominated the 'possible merge conflicts' list and distracted maintainers. Pull it out into a dedicated section at the very bottom of the report that separates PRs whose only overlap is DIRECTORY.md (safe to 'accept both' in the GitHub UI) from PRs that also collide on real source files. Also render the Script path relative to the git root instead of an absolute machine-specific path. --- scripts/pr_file_map.py | 168 +++++++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 24 deletions(-) diff --git a/scripts/pr_file_map.py b/scripts/pr_file_map.py index d384de75e12f..b245b66f863d 100644 --- a/scripts/pr_file_map.py +++ b/scripts/pr_file_map.py @@ -5,15 +5,24 @@ Lists all open pull requests in the current directory's git repo (via `gh`) and, for each file touched by any open PR, which PR number(s) touch it. -Output is GitHub-flavored Markdown that includes this script's path, the -current UTC datetime, and summary counts for open PRs, file touches, distinct -files, and existing/missing files. It highlights files touched by more than one -open PR first (the likely merge-conflict hot spots when landing PRs), then -renders a sorted list of files that currently exist in the working directory, -each with its modifying PR numbers, followed by a separate section for files -referenced by open PRs but that do not exist in the working directory (e.g. +Output is GitHub-flavored Markdown that includes this script's path (relative to +the git root), the current UTC datetime, and summary counts for open PRs, file +touches, distinct files, and existing/missing files. It highlights files touched +by more than one open PR first (the likely merge-conflict hot spots when landing +PRs), then renders a sorted list of files that currently exist in the working +directory, each with its modifying PR numbers, followed by a separate section for +files referenced by open PRs but that do not exist in the working directory (e.g. deleted, renamed, or on a branch not checked out locally). +`DIRECTORY.md` is treated specially and reported in its own section at the very +bottom. It is auto-generated, so nearly every PR touches it and it would +otherwise dominate the "possible merge conflicts" list and distract busy +maintainers. A merge conflict caused only by `DIRECTORY.md` is trivial to clear: +choose __accept both__ in the GitHub UI. The bottom section therefore separates +the PRs whose only overlap with other open PRs is `DIRECTORY.md` (safe to accept +both) from those that also overlap on real source files (which need a genuine +review or rebase). + Two file totals are reported because they answer different questions: - "file touches" counts every (PR, file) pair, so a file edited by three open PRs contributes three touches; and @@ -40,6 +49,10 @@ from datetime import UTC, datetime from pathlib import Path +# Auto-generated index of the repo. Almost every PR touches it, so a merge +# conflict here is expected and is resolved with "accept both" in the GitHub UI. +DIRECTORY_FILE = "DIRECTORY.md" + def run_gh(args: list[str]) -> str: try: @@ -68,6 +81,35 @@ def check_gh_auth() -> None: sys.exit("Error: gh is not authenticated. Run 'gh auth login' first.") +def git_root() -> Path | None: + """Return the repository root, or None if not inside a git work tree.""" + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], # noqa: S607 + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return None + except subprocess.CalledProcessError: + return None + root = result.stdout.strip() + return Path(root) if root else None + + +def script_display_path() -> Path: + """This script's path relative to the git root (falls back to absolute).""" + script_path = Path(__file__).resolve() + root = git_root() + if root is not None: + try: + return script_path.relative_to(root.resolve()) + except ValueError: + pass + return script_path + + def get_open_prs() -> list[dict]: raw = run_gh( ["pr", "list", "--state", "open", "--limit", "1000", "--json", "number,title"] @@ -81,6 +123,76 @@ def get_pr_files(pr_number: int) -> list[str]: return [f["path"] for f in data.get("files", [])] +def split_directory_conflicts( + directory_prs: list[int], + pr_to_files: dict[int, list[str]], + contested: dict[str, list[int]], +) -> tuple[list[int], list[tuple[int, list[str]]]]: + """Split PRs touching DIRECTORY.md by whether it is their only overlap. + + Returns (directory_only, directory_plus_other) where directory_only lists + PRs whose sole collision with other open PRs is DIRECTORY.md (safe to + "accept both"), and directory_plus_other pairs each remaining PR with the + other contested files it touches (a real review/rebase is needed). + """ + directory_only: list[int] = [] + directory_plus_other: list[tuple[int, list[str]]] = [] + for pr_number in directory_prs: + other_contested = sorted( + path + for path in pr_to_files.get(pr_number, []) + if path != DIRECTORY_FILE and path in contested + ) + if other_contested: + directory_plus_other.append((pr_number, other_contested)) + else: + directory_only.append(pr_number) + return directory_only, directory_plus_other + + +def render_file_section(title: str, files: dict[str, list[int]]) -> None: + """Render a Markdown section listing files and the PR numbers touching them.""" + print(f"\n## `{len(files)}` {title}\n") + if not files: + print("_None._") + return + for path in sorted(files): + pr_list = " ".join(f"#{n}" for n in files[path]) + print(f"- `{path}`: {pr_list}") + + +def render_directory_section( + directory_prs: list[int], + directory_only: list[int], + directory_plus_other: list[tuple[int, list[str]]], +) -> None: + """Render the bottom DIRECTORY.md section (kept last on purpose).""" + print(f"\n## `{len(directory_prs)}` open PRs touch `{DIRECTORY_FILE}`\n") + if not directory_prs: + print(f"_None -- no open PR modifies `{DIRECTORY_FILE}`._") + return + print( + f"`{DIRECTORY_FILE}` is auto-generated, so nearly every PR touches it. " + "A merge conflict caused only by this file is cleared by choosing " + "__accept both__ in the GitHub UI -- no rebase needed.\n" + ) + print( + f"### `{len(directory_only)}` PRs whose only overlap is " + f"`{DIRECTORY_FILE}` (safe to accept both)\n" + ) + print(" ".join(f"#{n}" for n in directory_only) if directory_only else "_None._") + print( + f"\n### `{len(directory_plus_other)}` PRs that also overlap on other " + "files (need a review or rebase)\n" + ) + if not directory_plus_other: + print("_None._") + return + for pr_number, files in directory_plus_other: + file_list = ", ".join(f"`{path}`" for path in files) + print(f"- #{pr_number}: also touches {file_list}") + + def main() -> None: if shutil.which("gh") is None: sys.exit("Error: 'gh' (GitHub CLI) is not installed or not in PATH.") @@ -92,11 +204,13 @@ def main() -> None: print(f"PR count from get_open_prs(): {pr_count}", file=sys.stderr) file_to_prs: dict[str, list[int]] = defaultdict(list) + pr_to_files: dict[int, list[str]] = {} touch_count = 0 # every (PR, file) pair; a file may be touched by many PRs for pr in prs: pr_number = pr["number"] pr_files = get_pr_files(pr_number) + pr_to_files[pr_number] = pr_files touch_count += len(pr_files) for path in pr_files: file_to_prs[path].append(pr_number) @@ -107,6 +221,10 @@ def main() -> None: file=sys.stderr, ) + # Pull DIRECTORY.md out so it does not dominate the contested/existing lists; + # it gets its own section at the very bottom. + directory_prs = sorted(set(file_to_prs.pop(DIRECTORY_FILE, []))) + existing: dict[str, list[int]] = {} missing: dict[str, list[int]] = {} contested: dict[str, list[int]] = {} @@ -121,18 +239,30 @@ def main() -> None: missing_count = len(missing) print( f"Existing files: {existing_count}, Missing files: {missing_count}, " - f"Contested files: {len(contested)}", + f"Contested files: {len(contested)} (excluding {DIRECTORY_FILE}), " + f"PRs touching {DIRECTORY_FILE}: {len(directory_prs)}", file=sys.stderr, ) + # Of the PRs that touch DIRECTORY.md, separate those whose only overlap with + # other open PRs is DIRECTORY.md itself (safe "accept both") from those that + # also collide on real source files (need a genuine review or rebase). + directory_only, directory_plus_other = split_directory_conflicts( + directory_prs, pr_to_files, contested + ) + # --- Render GitHub-flavored Markdown --- print("# Open Pull Request File Map\n") - print(f"- Script: `{Path(__file__).resolve()}`") + print(f"- Script: `{script_display_path()}`") print(f"- Generated (UTC): `{datetime.now(UTC).isoformat()}`") print(f"- Number of PRs: `{pr_count}`") print(f"- File touches (PR x file): `{touch_count}`") print(f"- Distinct files touched: `{distinct_count}`") - print(f"- Files touched by more than one PR: `{len(contested)}`") + print( + f"- Files touched by more than one PR: `{len(contested)}` " + f"(excluding `{DIRECTORY_FILE}`)" + ) + print(f"- Open PRs touching `{DIRECTORY_FILE}`: `{len(directory_prs)}`") if pr_count == 0: print("\nNo open pull requests found.") return @@ -150,21 +280,11 @@ def main() -> None: else: print("_None -- no open PRs overlap on the same file._") - print(f"\n## `{existing_count}` existing files\n") - if existing: - for path in sorted(existing): - pr_list = " ".join(f"#{n}" for n in existing[path]) - print(f"- `{path}`: {pr_list}") - else: - print("_None._") + render_file_section("existing files", existing) + render_file_section("files not present in the working directory", missing) - print(f"\n## `{missing_count}` files not present in the working directory\n") - if missing: - for path in sorted(missing): - pr_list = " ".join(f"#{n}" for n in missing[path]) - print(f"- `{path}`: {pr_list}") - else: - print("_None._") + # DIRECTORY.md section, kept at the very bottom on purpose. + render_directory_section(directory_prs, directory_only, directory_plus_other) if __name__ == "__main__": From aaf291657a836c49fffa758741962020cce3dd70 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:11:00 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/pr_file_map.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/pr_file_map.py b/scripts/pr_file_map.py index b245b66f863d..f9ea85012d4c 100644 --- a/scripts/pr_file_map.py +++ b/scripts/pr_file_map.py @@ -101,8 +101,7 @@ def git_root() -> Path | None: def script_display_path() -> Path: """This script's path relative to the git root (falls back to absolute).""" script_path = Path(__file__).resolve() - root = git_root() - if root is not None: + if (root := git_root()) is not None: try: return script_path.relative_to(root.resolve()) except ValueError: