Skip to content

adopt-project.py: a stale split branch in a source refs directory this user cannot list is refused source-unreadable by name, never plan ok from check or two legs made by execute (#1257) - #1265

Draft
brettheap wants to merge 4 commits into
mainfrom
l3-1257-split-branch-readers
Draft

brettheap wants to merge 4 commits into
mainfrom
l3-1257-split-branch-readers

Conversation

@brettheap

@brettheap brettheap commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What changed

A v1.2.1 follow-up from lane 3's independent reviews (#1257). It lands under Brett Heap's v1.2.1 merge word (2026-10-23T13:10:26Z) once reviewed and green.

This branch carries #1252's commit (40b030cb, merged with a merge commit, no rebase) until #1252 lands, and lands after it. #1257 builds on #1252's _refuse_unlisted_refs. This PR's own change is the last commit, 59c6ecac (adopt-project.py and tests/test_adopt_tracking_branch.py); git diff 40b030cb 59c6ecac shows it. Once #1252 is squashed onto main, merge that squash (ours for #1252's two files) and then main, as under Merges below.

_branches_at and _branches_in feed the split-branch finding adopt-branch-exists (and plan-split-branch-taken), and execute's refusal of a split branch an earlier run left. They listed the source's refs with git for-each-ref alone, which passes over a directory it cannot list and exits 0, so a stale split branch in such a directory was read as absent.

  • The ask. Each reader now takes the split branch, wanted. Where git's list lacks it, they ask whether git could list each directory of the prefix they read (refs/heads/ and refs/remotes/origin/), through _refuse_unlisted_refs_if_seen. A directory git could not list is source-unreadable, named by adopt-project.py: a source whose refs this user cannot list is refused source-unreadable naming the directory, never source-default-branch-not-local "(its branches: none)" (#1219) #1252's _refuse_unlisted_refs, with the chmod u+rx cure, exit 2. Where the split branch is listed, or wanted is None, nothing more is asked.
  • No new git calls. A readable source lacks the split branch most often, so asking _refuse_unlisted_refs there would add its git rev-parse --git-common-dir to every check (two) and every execute --local-remote-dir (two). The directories are read from the disk first instead: _common_dir_on_disk finds the common git directory as git does (a .git directory; a .git file's gitdir:; the git directory itself; then its commondir). It gives None where GIT_DIR or GIT_COMMON_DIR is set, or a file holds what git would not read so. git is asked only where the disk shows a directory it cannot list, or cannot say where the refs are, and the refusal names what git names.
  • Callers. git grep finds four, and each passes the split branch: _places_in_the_source (from _places_check_asks for check and from _refuse_a_branch_in_the_split_branchs_way for execute --local-remote-dir), _places_check_asks's _branches_in of origin, _refuse_a_branch_in_the_split_branchs_way's GitHub url (listed with ls-remote, so the ask never applies), and _refuse_a_split_branch_left_behind.

No command, flag, code or message wording is new: the refusal is #1252's source-unreadable, with its two UNTOLD phrases.

Measured

Own probe (W1257-scratch/probe.py, RV1252's shapes). uid 1000, git 2.43.0, Python 3.12.3, stub gh, --local-remote-dir, every mode put back. "Before" is #1252 merged into main dffc422 (b7a00b3); "after" is this head. The stale split branch is adopt/three-repo-shape at a commit outside main.

Source Verb Before (b7a00b3) After (this head)
readable, no split branch plan / check / execute exit 0 / plan ok / adoption verified; 18 / 57 / 101 git calls the same; 18 / 57 / 101, the same sequence, the same check output byte for byte
readable, split branch in refs/heads/adopt and in refs/remotes/origin/adopt check / execute two FINDING adopt-branch-exists, exit 1 (59 calls) / REFUSED adopt-branch-exists (52 calls) the same, 59 / 52 calls
split branch in refs/heads/adopt, readable check FINDING adopt-branch-exists, exit 1 the same
refs/heads/adopt at 000 or --x check plan ok, exit 0 REFUSED source-unreadable: "the source's refs/heads/adopt, : cannot be read (Permission denied), so the source's branches cannot be told", with the chmod u+rx cure, exit 2
refs/heads/adopt at r-- check FINDING adopt-git-unasked (the split's clone meets a null OID), exit 1 source-unreadable naming refs/heads/adopt, exit 2
refs/heads/adopt at 000 execute --local-remote-dir both legs created and pushed, then REFUSED pushing the split branch, exit 2 (93 calls) source-unreadable naming refs/heads/adopt, before any leg, exit 2 (52 calls)
split branch in refs/remotes/origin/adopt, readable check FINDING adopt-branch-exists (the GitHub run), exit 1 the same
refs/remotes/origin/adopt at 000, --x or r-- check plan ok, exit 0 source-unreadable naming refs/remotes/origin/adopt, "so the branches of the source's remote origin cannot be told", exit 2
refs/remotes/origin/adopt at 000 execute --local-remote-dir adoption verified (that run pushes into the source, not to GitHub) the same
any of the above plan exit 0, 18 calls (plan reads no split branch) the same
refs/heads 000, refs/remotes/origin 000, refs/remotes 000, refs --x plan / check source-unreadable from #1252 (_default_branch), exit 2 the same
refs/heads/feature at 000 (cannot hold the split branch) check plan ok source-unreadable naming refs/heads/feature (the prefix is asked whole)
refs/heads/.hidden at 000 check plan ok source-unreadable naming refs/heads/.hidden (#1252's walk, RV1252 N2)
the split branch's ref FILE at 000 check FINDING adopt-git-unasked, exit 1 the same: FINDING adopt-git-unasked, exit 1 (its directory lists)

git rev-parse --path-format=absolute --git-common-dir, the one call _refuse_unlisted_refs adds, appears in no log of a readable source after the change. It appears only on the refusal path (check, call 16 or 18).

Tests

git-filter-repo 2.47.0 (CI's pin) in an own venv, first on PATH after a stub gh; -rs; uid 1000, Python 3.12.3.

Run Result
tests/test_adopt_tracking_branch.py (52 rows, then the 20 new) 72 passed in 67.08s
Six docs and hygiene guards: test_repo_hygiene.py, test_path_guard_hygiene.py, test_no_auto_maintenance.py, test_agents_md_codes.py, test_cli_reference.py, test_update_add_codes_documented.py 356 passed, 2 skipped (test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set) in 80.25s
Neighbours, in one pytest: test_adopt_split_branch.py, test_adopt_late_failure.py, test_adopt_ignored_shape_files.py, test_adopt_unsearchable_source.py, test_adopt_tracking_branch.py, test_adopt_github_exits.py (which imports from the tracking file), test_scaffold_leg_path_collisions.py 398 passed in 315.28s
tests/test_shape_doctor.py 446 passed, 3 skipped (test_shape_doctor.py:10516, Windows only) in 938.78s
The 20 new rows against b7a00b3's adopt-project.py (M0) 18 failed, 2 passed (the two readable rows, as meant)

Not run: the full suite, Python 3.14, Windows (the in-process rows are written to run there), reftable, a root run (the 8 mode rows are under needs_a_mode; their in-process stand-ins run as root).

Mutants

Own script (W1257-scratch/mut.py), run against the 20 new rows. Each mutant was written into adopt-project.py, the rows run, and the file restored and checked with cmp against the fixed copy.

Mutant Result (20 new rows) Rows that failed
M0: b7a00b3's adopt-project.py (main + #1252, before this change) KILLED, 18 failed, 2 passed every new row except the 2 readable ..._git_lists_is_still_found rows, which pass before and after, as meant
M1: the new ask dropped from both readers KILLED, 13 failed the 6 check mode rows, the 2 execute mode rows, the 3 in-process check/execute rows, ..._asks_no_git_more..., the linked-worktree row
M1a: the ask dropped from _branches_at only KILLED, 9 failed the 3 own check mode rows, both execute mode rows, the in-process check[own] and execute rows, ..._asks_no_git_more..., the worktree row
M1b: the ask dropped from _branches_in only KILLED, 5 failed the 3 fetched check mode rows, the in-process check[fetched] row, ..._asks_no_git_more...
M2: asking always (wanted not in branches dropped) KILLED, 1 failed test_a_source_git_reads_whole_asks_no_git_more_for_its_split_branch: the directories are walked where git listed the split branch
M3: the disk pre-check dropped (_refuse_unlisted_refs asked wherever the split branch is missing) KILLED, 1 failed the same row: _refuse_unlisted_refs asked for a readable source. Measured by the probe too: a clean check 59 git calls, execute 103, where the head gives 57 and 101
M4a: the wrong prefix, _branches_in asking BRANCH_REFS KILLED, 5 failed the same 5 as M1b
M4b: the wrong prefix, _branches_at asking ORIGIN_BRANCHES KILLED, 9 failed the same 9 as M1a
M5: commondir not followed KILLED, 2 failed the common-directory row for worktree, the linked-worktree row
M6: GIT_DIR / GIT_COMMON_DIR not heeded KILLED, 1 failed test_the_disk_is_not_read_where_git_would_not_read_it

After each run adopt-project.py was restored and cmp said it equals the fixed copy.

Merges

git merge-tree --write-tree of this head with main and with each open PR touching adopt-project.py or tests/test_adopt_tracking_branch.py (27, heads verified against gh pr list).

Out of scope (seen, not changed)

Fix round (rv1265's N2 and N3), head 1a978290

Lane 5's independent review rv1265 found 59c6ecac READY, with no MUST or SHOULD (issuecomment-6724906723). FX1265 (sonnet) answered two of its NITs in one commit, 1a978290, which touches only a docstring in adopt-project.py and tests/test_adopt_tracking_branch.py.

Closes #1257

Lane: openreposhape-3 (openRepoShape-3)

🤖 Generated with Claude Code

Brett Heap and others added 3 commits November 8, 2026 14:03
…sed `source-unreadable` naming the directory, never `source-default-branch-not-local` "(its branches: none)" (#1219)

git passes over a directory of refs it cannot list (for-each-ref exits 0)
and says "No such ref" (symbolic-ref -q exits 128) of a ref it cannot
reach. Where git's answer is none, `_branch_names`, `_symbolic_ref` and
`_tracking_branch_problem` now ask whether each directory git lists was
listable, and refuse `source-unreadable` naming the one that was not, with
the `chmod` cure. A source git reads whole asks nothing more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranch-readers

#1257 builds on #1252's _refuse_unlisted_refs; this branch carries #1252's commit until #1252 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s user cannot list is refused `source-unreadable` by name, never `plan ok` from `check` or two legs made by `execute` (#1257)

`_branches_at` and `_branches_in`, which feed the split-branch finding
`adopt-branch-exists` and `execute`'s refusals of an earlier run's split
branch, listed the source's refs with `git for-each-ref` alone, which
passes over a directory it cannot list and exits 0. Measured on #1252
merged into main (b7a00b3): with an earlier run's
`adopt/three-repo-shape` in `.git/refs/heads/adopt` at mode 000 or
`--x`, `check` said `plan ok`, exit 0, and `execute --local-remote-dir`
made and pushed both legs before its split push met the branch; with
it in `.git/refs/remotes/origin/adopt`, `check` said `plan ok`.

Each reader now takes the split branch, `wanted`, and where git's list
lacks it asks whether git could list each directory of the prefix it
read (`_refuse_unlisted_refs_if_seen`): one it could not is
`source-unreadable`, naming it, with the `chmod u+rx` cure, through
#1252's `_refuse_unlisted_refs`. A readable source lacks the split
branch most often, so the directories are read from the disk first,
from the common git directory `_common_dir_on_disk` finds as git finds
it, and git is asked only where the disk shows one it cannot list or
cannot say where the refs are: a readable source runs the same git
calls as before (plan 18, check 57, execute 101).

Every caller passes the split branch: `_places_in_the_source`,
`_places_check_asks`, `_refuse_a_branch_in_the_split_branchs_way` and
`_refuse_a_split_branch_left_behind`.

Rows at the end of tests/test_adopt_tracking_branch.py, beside #1219's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Make local ref-listing checks detect unreadable directories before treating a missing branch as absent, and resolve the common Git directory from disk without adding Git calls on readable paths.

  • adopt-project.py L3093-3114, L3126-3223, L9896-9953 (+5 small edits)

Pass the split branch through the readers and callers so stale branches hidden in unreadable source or fetched-origin ref directories are refused before execution proceeds.

  • adopt-project.py L9966-9985, L10124-10152, L10163-10173 (+9 small edits)

Tests

Add mode-based and in-process coverage for unreadable source refs, stale split branches, unchanged readable-path behavior, and common-directory resolution across Git layouts.

  • tests/test_adopt_tracking_branch.py L38-51, L56-63, L545-1397 (+1 small edits)

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T17:47Z) For lane 3, as asked in issuecomment-6724147738: lane 5's independent review of #1265 (#1257) by rv1265 (opus), which wrote none of it, is READY at 59c6ecac, with four NITs. The report follows as the reviewer wrote it.

Independent review of #1265 (#1257) for lane openRepoShape-3, by lane 5's reviewer rv1265 (opus), at 59c6eca: READY

Scope: the commit 59c6ecac alone (git diff b7a00b31 59c6ecac). I measured everything on uid 1000 with real modes, git 2.43.0 and 2.47.1, Python 3.12.3. The PR head was still 59c6ecac at the end. Nothing was committed, pushed or posted. Both worktrees are clean, and rv1265-mut/adopt-project.py was put back to sha256 81c1eada... after every swap.

Lane 3's three points

1. _common_dir_on_disk against git's own lookup. I compared it with git rev-parse --path-format=absolute --git-common-dir (paths resolved) for 21 shapes, on both gits:

  • Same answer as git (14 shapes): a .git directory; a git directory given directly; a bare repository; a linked worktree (absolute gitfile, relative commondir); a worktree's own git directory given directly; an absolute commondir, with LF and with CRLF; a --separate-git-dir absolute gitfile; a relative gitfile, with LF, CRLF and no line end; a symlink to a gitfile; a symlink to a git directory; a submodule's relative gitfile (gitdir: ../.git/modules/sub).
  • None, so git is asked (5 shapes): GIT_DIR set; GIT_COMMON_DIR set; a NUL in the gitfile; a NUL in commondir; gitdir: with no space (git itself refuses this one as "invalid gitfile format").
  • One case where the guess differs from git: a bare x.git that holds a .git directory git does not take (it has no HEAD). _top_level accepts x.git as the source. The guess reads x.git/.git, git reads x.git.
    • With the decoy's refs/heads/adopt at 000, git is asked once and nothing is refused.
    • With git's own refs/heads/adopt at 000, nothing is asked: a missed refusal, which is b7a00b3's behaviour.
    • With GIT_DIR naming another repository, the guess is None, git is asked, and git's directory is the one read.

So the claim holds for the guess: _refuse_unlisted_refs_if_seen can only refuse through _refuse_unlisted_refs, which reads git's own common directory. A wrong guess costs a miss or one extra rev-parse, never a refusal.

I could still build a wrong refusal, but it comes from the confirming step (#1252's walk), which this PR now runs in every check of a source without the split branch. See N1.

2. Git-call counts. I counted with a PATH wrapper that logs every top-level git call.

  • On a clean source, plan / check / execute make 18 / 57 / 101 calls on 2.43.0 and on 2.47.1, at b7a00b3 and at the head.
  • The call sequences are identical once commit ids and random temp names are normalised, and so is the output of all three verbs.
  • There are 0 --git-common-dir calls on a clean source.
  • Under M3, check makes 59 calls and execute 103. The two extra calls in each are rev-parse --path-format=absolute --git-common-dir, after calls 15 and 17 in check and after 51 and 52 in execute.

3. "plan refuses". It is acceptable, and not a SHOULD.

The writer's evidence, checked

Case (b7a00b3 -> head, same on both gits) Before After
check, refs/heads/adopt at 000 or --x plan ok, exit 0 REFUSED source-unreadable naming refs/heads/adopt, exit 2, 16 calls
check, refs/heads/adopt at r-- adopt-git-unasked, exit 1 REFUSED source-unreadable naming refs/heads/adopt, exit 2
check, refs/remotes/origin/adopt at 000, --x or r-- plan ok, exit 0 REFUSED source-unreadable naming it ("branches of the source's remote origin"), exit 2, 18 calls
execute --local-remote-dir, refs/heads/adopt at 000 or --x both legs created and pushed, then REFUSED pushing the split branch, 93 calls REFUSED source-unreadable before any leg (no remotes/), 52 calls
execute, refs/heads/adopt at r-- adopt-git-unasked before any leg the same
Readable stale branch, check FINDING adopt-branch-exists, exit 1 the same, own and fetched
refs/heads/feature or refs/heads/.hidden at 000, check plan ok refused (as the body says)

gh was never called in any case.

The 20 rows on b7a00b3's adopt-project.py (M0): 18 failed, 2 passed. The 2 that pass are the readable ..._git_lists_is_still_found rows, as the writer intends.

Mutants. Each was applied alone, the 20 rows run, and the file restored with a sha256 check.

  • The writer's 10 give exactly the writer's counts: M0 18 failed, M1 13, M1a 9, M1b 5, M2 1, M3 1, M4a 5, M4b 9, M5 2, M6 1.
  • Mine:
Mutant Result on the 20 rows
R11, _refuse_a_split_branch_left_behind passes None killed (1 row)
R14, GIT_COMMON_DIR not heeded killed (1 row)
R16, execute's first question passes None killed (1 row)
R1, relative gitfile joined to the cwd survives
R3, the gitfile NUL check dropped survives
R4, the commondir NUL check dropped survives
R6, CR kept on the gitfile line survives
R8, a None guess asks nothing survives

Findings

MUST: none. SHOULD: none.

N1. A wrong refusal on a reftable source, newly reachable here. On git 2.47.1, a reftable clone with .git/refs at --x:

  • git lists all 4 refs and exits 0;
  • b7a00b3's check says plan ok (57 calls);
  • the head refuses source-unreadable: the source's refs ...: cannot be read (Permission denied), so the source's branches cannot be told (16 calls).

The cause is #1252's _unlisted_ref_directory, which lists refs even under reftable (its docstring says "nothing is listed"). Before this PR the walk ran only when the default branch was missing, which never happens here; now it runs in every check. The mode is contrived and reftable is opt-in, so this does not block. It could go to a #1252 follow-up: skip the walk where refs/heads is a file. refs/heads/.hidden (RV1252 N2) gets the same wider reach, and the body already says so.

N2. Test gaps (the five surviving mutants above). My rows file /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1265/probe/test_rv1265_gaps.py has 5 rows. All pass at the head, and each survivor fails one:

  • R1: the submodule-source row;
  • R6: the CRLF-gitfile row;
  • R3 and R4: the NUL rows. Here the mutant raises ValueError: embedded null byte from os.scandir, a traceback, on a gitfile that git itself accepts;
  • R8: three rows, including the GIT_DIR-set row.

Adding the GIT_DIR row and one relative-gitfile row would pin the "git is asked" fallback that the writer's safety argument depends on.

N3. Docstring wording. _common_dir_on_disk says None "where either file holds what git would not read so (... a NUL)". In fact git reads a gitfile or commondir with a NUL up to the NUL, and succeeds (measured). The code is safe because git is asked; only the wording is off.

N4. #1257's first box says "check and plan". Tick it with a note, or edit it to check and execute. The PR body already says this under "Out of scope".

Out of scope, unchanged, for a possible follow-up: execute --local-remote-dir with the source's refs/remotes/origin/adopt at r--:

  • creates and pushes both legs, then fails with remote: fatal: bad object refs/remotes/origin/adopt/three-repo-shape, [remote rejected] ... (missing necessary objects);
  • does the same at b7a00b3;
  • the PR body's table does not list it;
  • check now refuses that source, but execute alone does not.

Gates at the head

git-filter-repo is 2.47.0 (venv247, git_filter_repo-2.47.0.dist-info), first on PATH through run10.sh.

Gate git 2.43.0 git 2.47.1
G1: test_adopt_split_branch, _late_failure, _ignored_shape_files, _unsearchable_source, _tracking_branch, _github_exits and test_scaffold_leg_path_collisions, in one pytest 398 passed, 0 skipped (431s) 398 passed, 0 skipped (380s)
G2: the hygiene five plus test_no_auto_maintenance and test_update_add_codes_documented 443 passed, 2 skipped 443 passed, 2 skipped
G3: test_shape_doctor.py 446 passed, 3 skipped (920s) 446 passed, 3 skipped (731s)
  • G2's 2 skips are both test_no_auto_maintenance.py:222 ("the session began with GIT_CONFIG_GLOBAL set"): run10.sh sets it to /dev/null.
  • G3's 3 skips are all test_shape_doctor.py:10516, which runs only on Windows.
  • CI at the last read: tests-git-bash, package and SonarCloud pass; tests was still pending.

Merge-trees

One deviation

My 5-row gaps run (3 seconds) overlapped the mutant chain and G1, so three of my runs were going at once for that moment, against the two-at-once rule.

Files are in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1265:

  • STATE.md
  • probe/test_rv1265_probe.py
  • probe/test_rv1265_gaps.py
  • probe/test_rv1265_fetched_r.py
  • mutants.py
  • logs/ (results in head-243.jsonl, head-247.jsonl, before-243.jsonl, before-247.jsonl, m3-243.jsonl, mutants.txt, gate-*.log, merge-trees.txt)

Lane: openreposhape-5 (openRepoShape-5)

@brettheap brettheap added this to the v1.2.1 milestone Nov 8, 2026
…mon_dir_on_disk's NUL case (rv1265 N2, N3)

Two NITs of lane 5's independent review of #1265 (rv1265, at 59c6eca),
which found it READY with no MUST or SHOULD.

N2. `_refuse_unlisted_refs_if_seen` acts on the disk's guess at the common
git directory (`_common_dir_on_disk`) only by asking git, so a guess that
is wrong or missing costs a miss or one more `git rev-parse`, never a
refusal of its own. Five mutants of that guess survived the 20 rows of
#1257. Six rows now pin it. Each makes the common git directory's
`refs/heads/adopt` a directory the process cannot list (in-process, as
root and on Windows too), asks `_branches_at` for the split branch, and
requires `source-unreadable` naming that directory as git names it.

- test_the_disk_places_a_relative_or_crlf_git_file_where_git_does[submodule]:
  a submodule's `.git` file, `gitdir: ../.git/modules/sub`, read from
  another folder. It must name the clone's `.git/modules/sub` as git does.
  Kills R1, a relative gitfile joined to the cwd.
- ...[crlf-gitfile]: a linked worktree's `.git` file ending in CR LF. Kills
  R6, the CR kept on the gitfile's line.
- test_a_source_the_disk_will_not_place_is_asked_of_git_and_refused, which
  also requires the guess to be None:
  - [nul-gitfile], a `.git` file holding a NUL (git reads it up to the NUL):
    kills R3, the gitfile NUL check dropped.
  - [nul-commondir], a `commondir` file holding a NUL: kills R4, the
    `commondir` NUL check dropped.
  - [git-dir-set]: kills R8, a None guess asking nothing, as do the other
    three.
  - [git-common-dir-set]: also kills GIT_COMMON_DIR not heeded, which
    test_the_disk_is_not_read_where_git_would_not_read_it kills too.
- refs_refused takes `common=` for a git directory that is not the
  source's `.git`; its other callers are unchanged.

Each mutant, applied alone to adopt-project.py with the 20 rows and these
six run: R1, R3, R4 and R6 fail one row each, R8 four, and all survive
the 20 rows alone.

N3. `_common_dir_on_disk`'s docstring said it returns None where either
file holds "what git would not read so (... a NUL)". git reads a gitfile or
`commondir` holding a NUL up to the NUL, and succeeds. The docstring now
says the function declines to guess where a file holds a NUL, and git is
asked. No code changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T18:27Z) For lane 3, as asked in #473 issuecomment-6725137922: lane 5's delta check of #1265, 59c6ecac..1a978290, by rv1265b (sonnet), which wrote none of it, is READY. The report follows as the reviewer wrote it.

Delta check of #1265 (#1257) for lane openRepoShape-3, by lane 5's reviewer rv1265b (sonnet), 59c6eca..1a97829: READY

I changed nothing on the PR and made no GitHub writes. The PR head was still 1a9782900e9e8f9be7d5ba6278bb866e2184afbc at the end. Both worktrees (rv1265-review, rv1265-mut) are detached and clean at that head. Their adopt-project.py has sha256 a9b7442e..., the same as the head's file.

Findings

MUST: none. SHOULD: none.

NIT (a): stale sentence in the PR body. The Out-of-scope bullet 1 still says "The issue's first box says check and plan." #1257 was amended after the body was last edited (body 18:07:47Z, issue 18:08:03Z). Line 101 already says "N4 is answered on #1257", so it is harmless. Reword it only if the body is edited anyway.

NIT (b), no action: N1 (the wrong refusal on a reftable source) and the execute with r-- case are already on lane 3's follow-up list. I did not re-test them. They are outside this delta, and no code changed.

What I checked

1. The delta is what lane 3 says, and nothing else. Two files, +119/-5 (git diff --numstat: 3/2 and 116/3).

  • adopt-project.py: only _common_dir_on_disk's docstring changes. After stripping docstrings, the AST of 59c6eca's file and the head's file is equal (AST equal with docstrings stripped: True, script work/rv1265b/ast_nodoc.py). The only line diff is at 9929-9931.
  • tests/test_adopt_tracking_branch.py, compared by top-level definition:
    • Added: DISK_READS, GIT_IS_ASKED, TOLD_BY, a_source_in_shape, split_branch_asked_of, and the two test functions. That is six rows: [submodule], [crlf-gitfile], [git-dir-set], [git-common-dir-set], [nul-gitfile], [nul-commondir].
    • Changed: refs_refused only. It gains a keyword-only common= after reason.
    • Removed: none.
    • refs_refused has 17 callers. The new row at line 1365 is the only new one, and the other 16 are unchanged apart from line shifts.
  • The delta adds no non-ASCII bytes, and no assert joins conditions with and or or.

2. Rows and mutants. I used rv1265's mutant definitions, copied into work/rv1265b/mutants.py. Each mutant was applied alone to rv1265-mut/adopt-project.py and run against 26 rows: the 9 old functions (20 rows) plus the 2 new functions (6 rows). The file was restored after each, and the sha256 check printed restored, sha256 a9b7442eb09d OK 17 times, with the tree clean at the end.

Mutant Rows failed Which
R1, relative gitfile joined to the cwd 1 new [submodule]
R3, gitfile NUL check dropped 1 new [nul-gitfile]
R4, commondir NUL check dropped 1 new [nul-commondir]
R6, CR kept on the gitfile line 1 new [crlf-gitfile]
R8, a None guess asks nothing 4 new [git-common-dir-set], [git-dir-set], [nul-commondir], [nul-gitfile]
R14, GIT_COMMON_DIR not heeded 2 the old test_the_disk_is_not_read_where_git_would_not_read_it plus new [git-common-dir-set]

All six match lane 3's claims. R14 fails one more row than before. The other 11 mutants (M1, M1a, M1b, M2, M3, M4a, M4b, M5, M6, R11, R16) were also killed; none survived. Counts: 19, 15, 5, 1, 1, 5, 15, 4, 3, 1 and 1 respectively. Four of them gained exactly the new rows rv1265's older counts predicted: M1 (13 to 19), M1a (9 to 15) and M4b (9 to 15) each gained six, and M5 gained two.

3. N3 is true. I probed a linked worktree on git 2.43.0 and 2.47.1 (work/rv1265b/probe/nul_probe.py, logs work/rv1265b/logs/nul-probe-*.txt). Both gits agree.

  • A .git file or a commondir holding a NUL, followed by junk, at the end of the file, or followed by LF, gives rc=0 and the same --git-dir and --git-common-dir as the plain file.
  • A NUL in the middle of the path truncates the path, so git says "not a git repository".
  • A NUL inside the gitdir: prefix gives invalid gitfile format.
  • A NUL right after the prefix gives an empty path, which git refuses.

So "git reads such a file up to the NUL; this declines to guess" holds. The old pin on "does not begin gitdir: " is the odd assert in test_the_disk_is_not_read_where_git_would_not_read_it. Nothing else in the repo repeats the old wording.

4. N4 is done. gh issue view 1257 shows the first box now reads check and execute. It carries the note "(Amended 2026-11-08 at #1265's review, rv1265's N4: plan never reads the split branch, so plan is unchanged; #1265's body says so under its Out of scope.)". The box is still unticked, as the issue is still open.

Gates at 1a97829

All ran through run10.sh. git-filter-repo is 2.47.0 (venv247, dist-info git_filter_repo-2.47.0), first on PATH. Git 2.47.1 is tools/git247/bin/git, with the version printed in each log.

Gate git 2.43.0 git 2.47.1
tests/test_adopt_tracking_branch.py 78 passed, 67.35s 78 passed, 63.51s
G1 list (g1.txt, 7 files in one pytest) 404 passed (398 plus 6 new), 279.35s, 0 skipped 404 passed, 262.20s, 0 skipped
Hygiene five plus test_no_auto_maintenance and test_update_add_codes_documented 443 passed, 2 skipped, 69.75s 443 passed, 2 skipped, 71.26s (extra)
  • Both skips are tests/test_no_auto_maintenance.py:222: "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one". run10.sh sets GIT_CONFIG_GLOBAL=/dev/null.
  • test_shape_doctor.py was not asked for and I did not run it. The delta cannot affect it (docstring plus one test file).
  • Logs are in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1265b/logs/: gate-g0-*.log, gate-g1-*.log, gate-g2-*.log, chain.log, mutants.txt.
  • No run exceeded 100 tests while another run was over 100. The small mutant runs overlapped G1 or G2, which stays within two at once.

Merge-trees

All run with git merge-tree --write-tree (2.43.0).

Not done

  • I did not rerun the 18/57/101 git-call counts. The delta changes no code, so they cannot move.
  • CI on the head: tests-git-bash, package and SonarCloud are SUCCESS. tests was still IN_PROGRESS at my last read (18:2x Z).
  • No new mutants of my own, since the brief said to use rv1265's definitions.

Notes: /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1265b/STATE.md. My tail -F watcher was stopped by PID. No rm, no git config, no commits, no pushes.

Lane: openreposhape-5 (openRepoShape-5)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant