Skip to content

adopt-project.py and update-shape.py: a revision git is asked about ends its argv in --, so a root file named HEAD no longer stops execute with exit 128 before that leg is pushed, nor blanks what apply --branch says it committed (#1215) - #1294

Draft
brettheap wants to merge 3 commits into
mainfrom
fix/1215-rev-list-dashdash

Conversation

@brettheap

@brettheap brettheap commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Goal

adopt-project.py execute counts a leg's kept commits with git rev-list --count HEAD in the leg's work clone, right after git filter-repo, and that argv has no --. Where the leg keeps a root file named HEAD, git reads the argument as a revision and as a path, and refuses (fatal: ambiguous argument 'HEAD': both revision and filename, exit 128). execute stopped there, after git filter-repo and before that leg was pushed (#1215). The argv now ends in --.

The issue asked for the same shape to be looked for elsewhere. It is in four more places that a probe shows, in adopt-project.py and update-shape.py, and each ends in -- now, with its own row. Every other site of the shape in the shipped tools is listed below, with the reason it is left.

This PR changes adopt-project.py and update-shape.py, one -- per argv, and adds two test files. No refusal code changes, and no printed sentence changes except where git's own argv is echoed: a git-failed refusal from the two adopt sites now says git rev-list --count HEAD -- (or ... <id> --). Nothing in tests/, README.md, docs/ or AGENTS.md pins the old text (grepped again at ce9fbee6e).

What held on main (measured)

Probed at main 4552efa6a with git 2.43.0 and git-filter-repo 2.47.0, a stub gh first on PATH (exit 97), GH_TOKEN=bogus-l5, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null and local remotes only. The real gh was never run.

The issue's site. A source with a root file named HEAD (the plan asks which leg owns it, answered code, then spec):

  • plan exit 0 and check exit 0: neither warns. (check printed only its offline gh notes.)
  • execute --yes --local-remote-dir ... exit 2, REFUSED extracting the code leg. git-failed: git rev-list --count HEAD in <work>/Northwind-code exited 128: fatal: ambiguous argument 'HEAD': both revision and filename.
  • What an earlier leg left behind, which the issue had not measured: with HEAD in the code leg, the spec leg is pushed (refs/heads/main) and the code leg's bare repository is made and empty. With HEAD in the spec leg, both bare repositories are made and neither is pushed. A re-run is then refused leg-remote-exists, as the refusal's own NOTHING-rolled-back paragraph says.

The same shape, probed at the same base (each probe is one of the rows below, run against main's production code):

site on main what a name like the argument does there
adopt-project.py _extract_leg: rev-list --count HEAD a root file HEAD in a leg: execute exit 2 (above)
adopt-project.py Source.commit_count: rev-list --count <tip id> in the source, read in place a file named as the branch tip's id in the source's work tree (an untracked note, say): plan exit 2, REFUSED git-failed: git rev-list --count <id> ... ambiguous argument, no plan written
update-shape.py _land (which cmd_apply calls): diff-tree --root --no-commit-id -r --name-only --no-renames -z HEAD a root file HEAD: git answers nothing, _git_answer goes on without it, and the committed on <branch>: line names contracts/shape-pin.yaml, project.yaml, the paths the run was asked to commit, where the commit holds the pin alone. #674 had fixed exactly that line
update-shape.py _committed_pin: show HEAD:contracts/shape-pin.yaml a tracked file spelled HEAD:contracts/shape-pin.yaml (a directory named HEAD:contracts): the answer is empty, so the update-rerun-uncommitted and update-branch-uncommitted sentences lose which commit HEAD's pin names
update-shape.py _accepted_in_the_pin: show refs/remotes/origin/<base>:contracts/shape-pin.yaml a tracked file spelled so: the answer is [], so the pull request body names none of the copies --accept-local kept (#535)

What changed

Nothing else moves: every other line of both files is main's.

Rows, and what they do on main

row (ids) on main 4552efa6a at this head
test_execute_pushes_a_leg_that_keeps_a_root_file_named_head (no-such-file, spec-leg, code-leg): plan, answer, check, execute --yes with local remotes; adoption verified, exit 0, each leg's printed (N commits kept) equals rev-list --count main of the leg it pushed, and the leg that kept the file holds it spec-leg and code-leg fail (REFUSED extracting the spec leg / the code leg, exit 128 inside); no-such-file, the control, passes 3 pass
test_plan_and_check_read_a_source_holding_a_file_named_as_its_tip (no-such-file, a-file-named-as-the-tip): plan exit 0 and 3 commits, check says plan ok a-file-named-as-the-tip fails (plan exit 2 git-failed); the control passes 2 pass
test_the_committed_line_names_what_git_lists_over_a_file_named_head (no-such-file, a-file-named-head): apply --accept-local ... --branch, the line is committed on <branch>: contracts/shape-pin.yaml and git show --name-only agrees a-file-named-head fails (the line names the pin and project.yaml); the control passes 2 pass
test_the_commit_head_pins_is_read_over_a_file_spelled_as_the_argument (no-such-file, a-file-spelled-as-the-argument): _committed_pin is the pin's commit the spelled id fails (''); the control passes 2 pass
test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument (no-such-file, a-file-spelled-as-the-argument): _accepted_in_the_pin is ['scripts/bootstrap.py'] the spelled id fails ([]); the control passes 2 pass

On main: 6 failed, 5 passed over the 11 ids (adopt file: 3 failed, 2 passed, logs/base-adopt-rows.log; update-shape file: 3 failed, 3 passed, logs/base-us-rows.log): 6 rows fail on main and 5 are controls. The two colon-named rows (the a-file-spelled-as-the-argument ids of test_the_commit_head_pins_is_read_over_a_file_spelled_as_the_argument and test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument) skip on Windows, which cannot give a file a colon; both carry the skipif mark (the last section shows it read from pytest's own collection). Only the execute row needs git filter-repo, as tests/test_adopt_e2e.py does, and its 3 ids skip by name without it (git filter-repo is not installed: ...); the plan/check row's 2 ids run without it.

Mutants

Each applied alone in a detached tree, run against its own row file, and restored with git checkout -- <file>, its sha256 checked equal to the head's.

mutant result killed by
M1: _extract_leg back to rev-list --count HEAD (main's argv) killed (2 failed, 3 passed) test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[spec-leg]; test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[code-leg]
M1c: _extract_leg with the -- before HEAD: rev-list --count -- HEAD killed (3 failed, 2 passed) test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[no-such-file]; test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[spec-leg]; test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[code-leg]
M1d: _extract_leg counting head, the tip id, with no -- SURVIVES (5 passed) none
M2: Source.commit_count without the -- killed (1 failed, 4 passed) test_plan_and_check_read_a_source_holding_a_file_named_as_its_tip[a-file-named-as-the-tip]
M2c: Source.commit_count with the -- before the id killed (5 failed) test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[no-such-file]; test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[spec-leg]; test_execute_pushes_a_leg_that_keeps_a_root_file_named_head[code-leg]; test_plan_and_check_read_a_source_holding_a_file_named_as_its_tip[no-such-file]; test_plan_and_check_read_a_source_holding_a_file_named_as_its_tip[a-file-named-as-the-tip]
M3: _land's diff-tree (called by cmd_apply) without the -- killed (1 failed, 5 passed) test_the_committed_line_names_what_git_lists_over_a_file_named_head[a-file-named-head]
M3c: _land's diff-tree with the -- before HEAD killed (2 failed, 4 passed) test_the_committed_line_names_what_git_lists_over_a_file_named_head[no-such-file]; test_the_committed_line_names_what_git_lists_over_a_file_named_head[a-file-named-head]
M4: _committed_pin's show without the -- killed (1 failed, 5 passed) test_the_commit_head_pins_is_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument]
M5: _accepted_in_the_pin's show without the -- killed (1 failed, 5 passed) test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument]

One survivor, M1d, and it is equivalent. Counting head, the id rev-parse has just printed, fixes the issue the other way: git can read an id as a path only where the work tree holds an entry named as the leg's own rewritten tip, and no entry can be named for the commit that holds it. No row can tell it from HEAD --, so the row kills only the argv that is main's (M1) or has the -- in the wrong place (M1c). The form kept is -- after the revision. Each M-c mutant puts the -- first, which makes the revision a path: it fails the controls too.

The same shape elsewhere: what was scanned, and what is left

How. An AST scan of every list or tuple literal that holds a git subcommand which takes revisions, in adopt-project.py, update-shape.py, shape-doctor.py, scaffold-project.py, setup-project.py, scripts/*.py, templates/*/scripts/*.py and src/openreposhape/*.py; a second scan of every argv list that names HEAD; and a grep of install.sh, setup.sh, the template Makefiles and the workflows for git log|show|diff|rev-list|reset. The launchers bootstrap and openRepoShape hold none.

Which git commands trip. Raw git, in a repository whose work tree holds entries named HEAD, main and the tip's id (probe/amb2.sh, identical results on git 2.43.0 and 2.47.1): rev-list, log, show, diff, diff-tree, diff-index, reset, grep <tree>, bundle, format-patch and shortlog exit 128 ambiguous argument; checkout -B, ls-tree, read-tree, merge-base, cat-file, rev-parse, cherry-pick, describe, branch, archive, restore, merge, blame and show-branch exit 0. rev-list --count HEAD -- exits 0. So a revision passed to the second list is not at risk, and only the first list needs the --.

Fixed (the five above). Each is shown by a probe and has a row.

Left, and why:

  • Already end in --: adopt-project.py's LACKING_WALK walk (rev-list --objects --missing=print --branches --tags <head> --, adopt-project.py: check and execute run with GIT_NO_LAZY_FETCH=1 and refuse a partial-clone source that lacks an object its branches, tags or HEAD reach as source-partial-clone, so neither fetches into the source any more (#1145) #1180), the git log ... <tip> --branches --tags -- of _exported_gitlink, the git grep ... <tip> -- <paths> of the code-path scan; update-shape.py's diff --quiet HEAD -- <paths>; shape-doctor.py's two rev-list ... HEAD -- <pin> walks; scripts/bump-leg.py's reset -q HEAD -- <paths>; scripts/family.py's diff --quiet ... HEAD -- <paths>; scripts/repo_shape.py's ls-tree HEAD --.
  • Cannot trip (raw git exits 0 with an entry of that name): adopt-project.py's read-tree <tip>, both ls-tree ... <rev>, merge-base --is-ancestor <tip> <commit>, cat-file -e <id>^{commit}, and both checkout -q -B <branch> <tip> (the leg's work clone and the assembly's); shape-doctor.py's merge-base HEAD MERGE_HEAD, cat-file -t HEAD:./<path> and every rev-parse; the rev-parse, symbolic-ref, ls-tree, cat-file, for-each-ref and config calls of scripts/*.py, templates/*/scripts/*.py and src/openreposhape/standard.py. rev-list --count --all (scaffold-project.py, scripts/family.py) names no revision.
  • Printed, never run: update-shape.py's upstream_show, which hands a person git show <target>:<source> quoted for a shell.
  • Left: the revision is an id nothing can have been named for before the command asks about it.
    • scaffold-project.py _take_held_seed: reset -q <seed>. The seed is a commit an earlier run pushed, found on the remote and fetched, whose tree is what this run has just written to the work tree; an entry named as the seed's id would have to sit inside the seed's own tree, and no commit's tree holds an entry named for that commit.
    • scripts/bump-leg.py _committed_paths: diff-tree ... <head>, and scripts/family.py take_back: rev-list --parents --no-walk <made>. Each id is the commit the run has just made.
  • Left: only an entry placed by hand in the tool's own checkout could trip it. shape-doctor.py objects_lacking: rev-list --objects --no-walk --missing=print <commit>, where the commit is the one a project's pin names and the repository is the checkout of the standard. Raw git does refuse it with an entry of that name, but no project's content reaches that checkout, so no row built from a project can show it. Source.commit_count, by contrast, asks the person's own source, in place, which may hold anything, and is fixed above.

Runs

Two gates are reported. The first, the full one below, ran at 70747e4ab, this PR's first commit. The second ran after rv1294's review at ce9fbee6e (the last section), on the files the round touches.

Gate files at 70747e4ab (work/n1215/gate-files.txt, 78 files, 5980 ids in 60 parts of at most 100, -rs alone, at the head 70747e4ab): my two test files; every adopt test file that runs execute (63 files by grep, gate-adopt-exec.txt, my adopt file among them); tests/test_shape_doctor.py and tests/test_scaffold_leg_path_collisions.py; the hygiene five (test_path_guard_hygiene, test_repo_hygiene, test_agents_md_codes, test_cli_reference, test_pinned_copy_text); tests/test_no_auto_maintenance.py; and seven update-shape files for the changed lines (test_update_shape, _apply_lines_and_headers, _landing, _partial_rollback, _pr_base, _messages, _next_quoting, with _merged_away already in the adopt list). git 2.43.0 is the system git; git 2.47.1 is a build of kernel.org's tarball. git-filter-repo 2.47.0 on both (L/tools/venv247; its --version prints a40bce548d2c), Python 3.12.3.

git parts run ids passed skipped failed
2.43.0 60 of 60 5980 5960 20 0
2.47.1 60 of 60 5980 5960 20 0

The gate is finished on both gits: every one of the 60 parts has one summary line with exit 0 and no failure, and work/n1215/gates.summary holds the START and END lines. The chain ran in three stretches (parts 00-14 from 21:26Z; parts 15-25 from 22:04Z; parts 26-59 from 22:55Z), because it was stopped at LAST STEP and again at the account swap; each START line records head=70747e4abcae and dirty=0, so every part ran at this head, once to its summary line. The per-part logs are work/n1215/logs/g<git>-part-NN.log.

My two row files alone (the rows above, 11 ids): git 2.43.0 (logs/fix-adopt-rows.log, logs/fix-us-rows.log): 5 + 6 passed; git 2.47.1 (logs/rows-2471.log): 11 passed.

The 11 rows at the predicted merge. Main moved five landings past this head's base (see Merges), so the head was merged with origin/main 370a23cd2 as git commit-tree would make it (fd3a8972d, tree b6570f985ee8, the tree git merge-tree --write-tree wrote; unreferenced, parents 70747e4ab and 370a23cd2). Against 370a23cd2 it differs in the same four files, +297/-6, and the stable patch-id of that diff (7227aad7ce4c) equals the head's diff against 4552efa6a, file by file (adopt-project.py c4844e230193, update-shape.py 3d865b5ea243, tests/test_adopt_rev_list_dashdash.py 0d6d249f3721, tests/test_update_shape_head_named_entries.py 64afdbb6f354). The two new test files, run there on git 2.43.0 (work/n1215b/logs/pm-rows-243.log): 11 passed, none skipped, none failed.

Skips by name (from the SKIPPED lines of each part's log, -rs alone; [N] is pytest's count). None is in either new test file:

  • git 2.43.0, 20 skips:

    • SKIPPED [1] tests/test_adopt_check_throwaway_repos.py:482: needs a disk that ignores case: 'git init' wrote no 'core.ignorecase=true' in the work directory, so that disk keeps case; the rows above write the setting into the throwaway repository by hand instead
    • SKIPPED [1] tests/test_adopt_eol_block_through_links.py:263: only a root that keeps a link has one to write through
    • SKIPPED [1] tests/test_adopt_eol_block_through_links.py:284: only a root that keeps a link has one to replace
    • SKIPPED [1] tests/test_adopt_root_symlinks.py:1960: the reftable backend needs git 2.45 or later
    • SKIPPED [1] tests/test_adopt_root_symlinks.py:2015: the reftable backend needs git 2.45 or later
    • SKIPPED [1] tests/test_adopt_root_symlinks.py:2596: the reftable backend needs git 2.45 or later
    • SKIPPED [1] tests/test_adopt_template_values.py:235: 'git config user.name' here is not read from GIT_CONFIG_GLOBAL: b'\n'
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:459: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:472: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:481: the templates read ~, 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:498: the templates read 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:538: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:549: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:558: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:581: the templates read 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one
    • SKIPPED [3] tests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows host
  • git 2.47.1, 20 skips:

    • SKIPPED [1] tests/test_adopt_check_throwaway_repos.py:482: needs a disk that ignores case: 'git init' wrote no 'core.ignorecase=true' in the work directory, so that disk keeps case; the rows above write the setting into the throwaway repository by hand instead
    • SKIPPED [3] tests/test_adopt_check_throwaway_repos.py:1041: this git's 'git clone' takes 'init.defaultBranch = a..b', as 2.55's does: the split clones there
    • SKIPPED [1] tests/test_adopt_eol_block_through_links.py:263: only a root that keeps a link has one to write through
    • SKIPPED [1] tests/test_adopt_eol_block_through_links.py:284: only a root that keeps a link has one to replace
    • SKIPPED [1] tests/test_adopt_template_values.py:235: 'git config user.name' here is not read from GIT_CONFIG_GLOBAL: b'\n'
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:459: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:472: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:481: the templates read ~, 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:498: the templates read 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:538: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:549: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:558: got empty parameter set for (value, reading)
    • SKIPPED [1] tests/test_leg_path_yaml_scalars.py:581: the templates read 2026 back (#259 quotes the leg-path lines), so the rule has nothing to refuse
    • SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one
    • SKIPPED [3] tests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows host

Merges

Head ce9fbee6e (this PR's two commits: 70747e4ab, then the round after rv1294's review) is on main 4552efa6a. Main is 370a23cd2, fetched at 00:39Z on 2026-11-10 (no landing since the last body), five landings past the base (#1227, #1189, #1207, #1201, #1164). Fresh at 00:39Z: git merge-tree --write-tree <other> HEAD on git 2.47.1, with every other head read from gh pr list --state open (read only, 40 open PRs, 19 of them touching adopt-project.py, update-shape.py or one of the two new test files, this PR among them; heads in work/n1215c/mt-list.txt):

other rc note
origin/main 370a23cd2 0 clean, tree b4bcfb83eb75 (the predicted merge in the last section)
the 18 other open PRs that touch the four files 0 #1171, #1186, #1194, #1198, #1210, #1225, #1231, #1237, #1241, #1249, #1252, #1253, #1265, #1268, #1269, #1280, #1281, #1284

Every one merges clean. #1237 is now 87325bed2: at b557859fd it conflicted in tests/test_adopt_check_throwaway_repos.py, a file this PR does not change, and rv1294 measured it clean at 70747e4ab too. Raw output: work/n1215c/merge-trees.log (the earlier runs: work/n1215b/merge-trees.log, at 70747e4ab; work/n1215/merge-trees.log, at 4552efa6a).

The round after rv1294's review

rv1294 (opus) read this PR at 70747e4ab and found it READY: no MUST, two SHOULDs and six NITs (#1294 issuecomment-6755422831). The round is one commit, ce9fbee6e, on top of 70747e4ab (two files, +9/-5: a mark in tests/test_update_shape_head_named_entries.py and two comments in update-shape.py), and this body. adopt-project.py, tests/test_adopt_rev_list_dashdash.py and every production code line are as 70747e4ab had them. The title is unchanged.

In the commit

  • SHOULD 1, the third update-shape row had no Windows skip. The colon name of test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument is now pytest.param("refs/remotes/origin/{base}:" + PIN, marks=NOT_ON_WINDOWS), as the second row's is. On Windows it skips instead of failing at the mkdir in tracked(), which is reasoned, not measured: there is no Windows host here, as rv1294 said. Read from pytest's own collection (--collect-only, a plugin that prints each item's own skipif marks; work/n1215c/logs/marks-*.log):

    • at 70747e4ab:
      • test_the_commit_head_pins_is_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument] carries skipif(condition=False, reason='Windows cannot give a file a colon in its name')
      • test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument] carries no skipif mark
    • at ce9fbee6e:
      • test_the_commit_head_pins_is_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument] carries skipif(condition=False, reason='Windows cannot give a file a colon in its name')
      • test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument[a-file-spelled-as-the-argument] carries skipif(condition=False, reason='Windows cannot give a file a colon in its name')

    The condition prints False because this host is Linux; both rows use the one NOT_ON_WINDOWS object (os.name == "nt"). The module docstring ("THE SECOND AND THIRD FILE NAMES HOLD A COLON ... and are skipped there") and the Rows paragraph above are now true. No effect on CI, as rv1294 noted: the tests-git-bash job on windows-latest runs only test_openreposhape_command.py, test_install_sh.py and test_install_sh_filter_repo.py. A mark that only a Windows host reads cannot be killed by a mutant here, so there is none.

  • NIT 5, the update-shape comments. The two comments at _land's diff-tree and at _committed_pin said git refuses the argument "where the root tracks a file of that name". They now say that an entry of that name in the work tree, tracked or not, trips it, as Source.commit_count's docstring does. Comments only. Probed again with raw git 2.43.0 and 2.47.1 in a scratch repository, an UNTRACKED file named HEAD and an untracked HEAD:contracts/shape-pin.yaml (work/n1215c/probes/untracked.sh, logs/untracked.log): diff-tree --root --no-commit-id -r --name-only --no-renames -z HEAD and show HEAD:contracts/shape-pin.yaml exit 128 ambiguous argument; each with a trailing -- exits 0.

In this body only

  • SHOULD 2: the count on main was reversed. It is 6 failed and 5 passed over the 11 ids (adopt file 3 failed, 2 passed; update-shape file 3 failed, 3 passed), as rv1294 measured on git 2.43.0 and 2.47.1: 6 rows fail on main and 5 are controls. The Rows paragraph says so now; nothing else in the body carried the reversed pair.
  • NIT 1: the diff-tree site is in _land, which cmd_apply calls (the hunk header says def _land(); the "What held", "What changed" and Mutants rows say so.
  • NIT 2: Merges is refreshed above; adopt-project.py: the mount removes a path a case twin is at or under with git rm -f and writes the twins the root keeps back from the index, so on a disk that ignores case execute keeps a set the plan splits across repositories or drops (#1183) #1237 is 87325bed2 and merges clean.
  • NIT 3: only the execute row needs git filter-repo. With no git-filter-repo on PATH, git 2.43.0, at ce9fbee6e (work/n1215c/logs/nofr-adopt-rows.log): 2 passed, 3 skipped in 3.46s, with SKIPPED [3] tests/test_adopt_rev_list_dashdash.py:60: git filter-repo is not installed: 'pip install git-filter-repo' for the execute row's 3 ids, and the plan/check row's 2 ids passing.
  • NIT 4: scaffold-project.py _take_held_seed is still left, with the reason corrected: its seed is a commit an earlier run pushed, found on the remote and fetched, whose tree is what this run has just written. The conclusion holds.
  • NIT 6: a git-failed refusal from the two adopt sites now echoes git rev-list --count HEAD -- (and ... <id> --), so "no printed sentence changes" is true except there; the Goal says so. Grepped again at ce9fbee6e (work/n1215c/logs/pins-grep.log): nothing in tests/, README.md, docs/ or AGENTS.md pins the old text. The only hits for rev-list --count in them are in this PR's own adopt test file, which describes main's refusal and asserts none of it.

Runs this round, at ce9fbee6e (each START line records head=ce9fbee6ea40 and dirty=0; work/n1215c/gates.summary, logs work/n1215c/logs/g<git>-part-NN.log): 827 ids in 9 parts of at most 100, -rs alone, Python 3.12.3, git-filter-repo 2.47.0 on PATH for both (L/tools/venv247, --version prints a40bce548d2c), git 2.43.0 the system git and git 2.47.1 the kernel.org build, run names n1215c-g<git>-part-NN. The files: this PR's two test files; tests/test_update_shape_partial_rollback.py, whose module-scoped fixture the new update-shape file imports, in the same pytest as that file (part 00, with tests/test_update_shape_apply_lines_and_headers.py); the other update-shape files the body names (test_update_shape, _landing, _pr_base, _messages, _next_quoting, _merged_away); tests/test_adopt_e2e.py; the hygiene five (test_path_guard_hygiene, test_repo_hygiene, test_agents_md_codes, test_cli_reference, test_pinned_copy_text); and tests/test_no_auto_maintenance.py. tests/test_shape_doctor.py and tests/test_scaffold_leg_path_collisions.py, which the full gate ran at 70747e4ab, are not run again: the round changes no code in adopt-project.py or shape-doctor.py.

git parts ids passed skipped failed
2.43.0 9 of 9 827 825 2 0
2.47.1 9 of 9 827 825 2 0
  • Totals, from the logs: git 2.43.0 9 parts, 827 ids: 825 passed, 2 skipped, 0 failed; git 2.47.1 9 parts, 827 ids: 825 passed, 2 skipped, 0 failed.
  • Skips by name, git 2.43.0:
    • SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one
  • Skips by name, git 2.47.1:
    • SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one

The predicted merge again. Main is still 370a23cd2, so the head merged with it, as git commit-tree would make it, is baa7d4e46 (tree b4bcfb83eb75, the tree git merge-tree --write-tree wrote; unreferenced, parents ce9fbee6e and 370a23cd2). Its diff against 370a23cd2 is the same four files, +301/-6, and the stable patch-id of that diff (6bf5e6631df0) equals the head's diff against 4552efa6a, file by file (adopt-project.py c4844e230193, update-shape.py d27a158df865, tests/test_adopt_rev_list_dashdash.py 0d6d249f3721, tests/test_update_shape_head_named_entries.py c8885e14ecd5; work/n1215c/logs/pm-make.log). The two new test files, run there: git 2.43.0 11 passed in 19.05s; git 2.47.1 11 passed in 18.17s (work/n1215c/logs/pm-rows-*.log).

Closes #1215

Lane: openreposhape-5 (openRepoShape-5)

🤖 Generated with Claude Code

Summary by Sourcery

Harden adoption and shape-update Git operations against work-tree entries that conflict with revision arguments.

Bug Fixes:

  • Prevent Git revision/path ambiguity from breaking project adoption when files are named after revisions, including HEAD or a branch tip ID.
  • Ensure update-shape reports committed paths and reads shape-pin metadata correctly when work trees contain entries matching Git revision arguments.

Enhancements:

  • Harden revision-oriented Git commands in adopt-project.py and update-shape.py by explicitly separating revisions from paths.

Tests:

  • Add regression coverage for adoption and shape-update workflows involving files named HEAD, branch tip IDs, and revision-shaped paths.

…nds its argv in `--`, so a root file named `HEAD` no longer stops `execute` with exit 128 before that leg is pushed, nor blanks what `apply --branch` says it committed (#1215)

`git rev-list --count HEAD` (`_extract_leg`), `git rev-list --count <tip id>`
(`Source.commit_count`), `git diff-tree ... HEAD` (the `committed on` line)
and the two `git show <rev>:contracts/shape-pin.yaml` reads read their
revision as a path too where the work tree holds an entry of that name, and
git refuses it as ambiguous. Each argv now ends in `--`. The rest of the
shipped tools were scanned for the same shape; the sites left, and why, are
in the pull request.

Closes #1215

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.2.2 milestone Nov 9, 2026
@sourcery-ai

sourcery-ai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Separate revision arguments from paths in adoption commit counts so revision-like files cannot abort planning or leg extraction before a leg is pushed.

  • adopt-project.py L3368-3375, L15805-15810

End the shape-update revision queries with -- so files named like their revision arguments cannot suppress commit-path or pin information.

  • update-shape.py L5428-5435, L6039-6044, L6083-6085

Tests

Add adoption regressions for revision-like filenames in leg extraction and source commit counting, including controls without those files.

  • tests/test_adopt_rev_list_dashdash.py L1-135

Add shape-update regressions for revision-like root entries affecting the committed-path report and pin reads, including controls and Windows skips for colon names.

  • tests/test_update_shape_head_named_entries.py L1-138

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

Independent review of #1294 (#1215) for lane openRepoShape-5, by rv1294 (opus), at 70747e4: READY

Summary. The fix does what #1215 asks. The diff adds exactly five trailing --, plus comments and one docstring. At base, execute stops at git rev-list --count HEAD with exit 128; at the head it pushes both legs. All six rows that fail on main pass at the head, and the five controls pass both ways. The gates are clean on both gits, at the head and at a predicted merge with main 370a23c. No MUST. Two SHOULDs: a row's Windows skip is missing, and one count in the body is wrong. There are six NITs.

MUST: none.

SHOULD

  1. The third update-shape row has no Windows skip, though the file and the body say it does. In tests/test_update_shape_head_named_entries.py, the colon name "refs/remotes/origin/{base}:" + PIN in test_the_copies_a_run_kept_are_read_over_a_file_spelled_as_the_argument is a bare value with no mark. Only the second row's colon name has NOT_ON_WINDOWS.
    • The module docstring says "THE SECOND AND THIRD FILE NAMES HOLD A COLON ... and are skipped there". The body says "The two colon-named rows skip on Windows."
    • Measured from pytest's own collection (logs/marks.log): test_the_commit_head_pins_...[a-file-spelled-as-the-argument] carries skipif(condition=False, reason='Windows cannot give a file a colon in its name'); test_the_copies_a_run_kept_...[a-file-spelled-as-the-argument] carries "no skipif mark".
    • On Windows that row would fail at the mkdir in tracked() instead of skipping. This is reasoned, not measured: I have no Windows host.
    • No effect on CI: the tests-git-bash job on windows-latest runs only test_openreposhape_command.py, test_install_sh.py and test_install_sh_filter_repo.py.
    • Fix: pytest.param("refs/remotes/origin/{base}:" + PIN, marks=NOT_ON_WINDOWS).
  2. The body's count on main is reversed. Its line reads "On main: 5 failed, 6 passed over the 11 ids (adopt file: 3 failed, 2 passed; update-shape file: 3 failed, 3 passed)". Its own split adds up to 6 failed and 5 passed. I measured the same: BASE-243 and BASE-2471 each gave 6 failed, 5 passed. So there are 6 rows that fail on main and 5 controls. R29's "5 fail on main and 6 are controls" came from this line.

NIT

  1. The body and R29 put the diff-tree site in cmd_apply. It is in _land (update-shape.py:5248), which cmd_apply calls; the hunk header says def _land(.
  2. The Merges section is stale for adopt-project.py: the mount removes a path a case twin is at or under with git rm -f and writes the twins the root keeps back from the index, so on a disk that ignores case execute keeps a set the plan splits across repositories or drops (#1183) #1237. Its head moved from b557859fd to 87325bed2, and it now merges clean with the head (rc=0).
  3. The body says "The adopt rows need git filter-repo ... and skip without it". Only the execute row needs it. With no filter-repo on PATH (logs/nofr-adopt-rows.log), the execute row's 3 ids skip with the reason git filter-repo is not installed and the plan/check row's 2 ids pass. Better than claimed, but worded wrong.
  4. The reason given for leaving scaffold-project.py _take_held_seed (reset -q <seed>) says the seed is "the commit this scaffold made". It is a commit an earlier run pushed, found on the remote and fetched, whose tree is what this run just wrote. The conclusion still holds: an entry named as the seed's id would have to be inside the seed's own tree.
  5. The update-shape comments say the argument is ambiguous "where the root tracks a file". Git checks the work tree, so an untracked entry trips it too; my probe used only untracked entries. The Source.commit_count docstring words this correctly.
  6. "No printed sentence changes" is true except where git's argv is echoed. A git-failed refusal from these two adopt sites would now print rev-list --count HEAD --. I found nothing in tests/, README.md, docs/ or AGENTS.md that pins the old text.

Evidence, judge by judge

  1. The fix against adopt-project.py: execute counts a leg's commits with git rev-list --count HEAD and no --, so a leg that keeps a root file named HEAD stops execute with exit 128 after git filter-repo, before that leg is pushed #1215. The production diff against 4552efa6a is five argvs. Each -- comes after the revision:

  2. The rows, with main's two production files swapped into rv1294-mut and restored by sha256:

    • Each base failure is for the claimed reason: REFUSED extracting the spec leg / the code leg. git-failed: git rev-list --count HEAD ... exited 128; REFUSED git-failed: git rev-list --count <id> ... ambiguous argument; the committed line naming contracts/shape-pin.yaml, project.yaml; '' == the expected commit; and []. The 5 controls pass.
    • At base, the failed rows' retained directories match the body's "earlier leg" claim:
      • with HEAD in the spec leg, both bare repositories exist with no refs;
      • with it in the code leg, the spec leg holds refs/heads/main (2 commits) and the code leg's repository is empty;
      • the plan/check row wrote no plan.
  3. The sites left.

    • Raw git, with entries named exactly as each argument, gave the same results on 2.43.0 and 2.47.1 (probes/amb2-*.log):
      • all 11 commands on the body's "trip" list exit 128;
      • every command on its "cannot trip" list exits 0, as do the checkout -q <id>, --detach, -b, plain-branch and -B forms;
      • the five fixed argvs exit 0.
      • --end-of-options does not help: still exit 128.
    • My own scan matches n1215's list (probes/scan-head.txt). I ran an AST pass over all 24 shipped .py files, a grep of every quoted risky subcommand, and a grep of the shell files and workflows.
      • The only unguarded revision argvs are the four the body leaves: _take_held_seed, bump-leg _committed_paths, family take_back, and shape-doctor objects_lacking. The last reads SHAPE_ROOT, the doctor's own checkout. I read all four and agree none can be tripped by a project's content.
      • Extra forms the tools hand a revision to all exit 0 with a same-named entry (probes/ambm-*.log): for-each-ref --contains, branch -f, merge --ff-only (bootstrap.py, in a leg), merge-tree, describe and ls-files --with-tree.
      • The workflows' git log/git show calls run only in the standard's own checkout.
  4. Mutants, each applied alone in rv1294-mut against its row file on git 2.43.0, restored by git checkout, sha256 checked equal:

    mutant rows failed rows passed result
    M1 2 3 killed
    M1c 3 2 killed
    M1d 0 5 survives
    M2 1 4 killed
    M2c 5 0 killed
    M3 1 5 killed
    M3c 2 4 killed
    M4 1 5 killed
    M5 1 5 killed

    All of these match n1215's table. My own, also killed by their rows:

    • M4c and M5c (-- before the revision) each fail 2: the row and its control.
    • M1e through M5e (--end-of-options before the revision) each fail the row only.

    M1f (the issue's head, "--") survives, as an alternative correct fix should.

    M1d is equivalent, as claimed. A probe (probes/fr2.log) shows git filter-repo 2.47.0, run as _extract_leg runs it, leaves only the leg's kept paths in the work tree: nothing untracked and nothing ignored. So the only entry that could be named as the leg's tip id is one inside that tip's own tree. That is a hash fixed point, so it cannot happen. The same probe confirms the M1d argv would trip if such an entry existed, and the head's argv would not.

  5. Gates at the head 70747e4 in rv1294-review: 19 files, 1380 ids, 14 parts of at most 100, -rs alone, Python 3.12.3, git-filter-repo a40bce548d2c (2.47.0, L/tools/venv247). Summary in gates.summary, logs logs/g<git>-part-NN.log.

    git parts ids passed skipped failed
    2.43.0 (/usr/bin/git) 14 1380 1375 5 0
    2.47.1 (L/tools/git247/bin/git) 14 1380 1375 5 0
    • The files: both new test files; test_adopt_e2e; the eight update-shape files; test_shape_doctor; test_scaffold_leg_path_collisions; the hygiene five; test_no_auto_maintenance.
    • Part 00 put the new update-shape file in one pytest with test_update_shape_partial_rollback.py, whose module-scoped fixture it imports, and _apply_lines_and_headers.
    • The skips, the same on both gits: SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one; SKIPPED [3] tests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows host.
    • The body's own gate totals (5980 ids in 60 parts, 5960 passed, 20 skipped, 0 failed on each git, each part run once) match n1215's per-part logs.
  6. The predicted merge af631d544 (unreferenced commit-tree, tree b6570f985ee8, parents 70747e4ab and 370a23cd2; main fetched fresh). Its stable patch-id against main, 7227aad7ce4c, equals the head's against 4552efa6a. Summary in pm.summary.

    • The 11 rows: 11 passed on git 2.43.0 and on 2.47.1.
    • Main's changed test files that name the keywords, 7 files and 471 ids in 5 parts on 2.43.0: 469 passed, 2 skipped, 0 failed. The files: test_adopt_check_throwaway_repos (196), _collision_make_reads (28), _e2e (27), _make_bootstrap_followups (35), _make_reads_first (34), _next_make_reads_first (38), test_family (113).
    • The skips: test_adopt_check_throwaway_repos.py:498 and :966, each "needs a disk that ignores case".
    • No failure, so there was nothing to compare with main alone. test_adopt_symlink_target_bytes.py is not among main's changed test files since 4552efa6a.
  7. Merge-trees on git 2.47.1 at 00:08Z (merge-trees.log). The head merges clean with main 370a23c (tree b6570f985ee8). Of 40 open PRs, 18 touch the four files, heads read fresh, and all 18 merge clean with the head (rc=0): adopt-project.py and repo_shape.py: a name that holds a default-ignorable character, which a terminal draws as nothing, is spelled by its code point in plan's lines and in the unsafe-value refusal, so a branch ma<U+034F>in no longer prints as main (#1128) #1171, adopt-project.py: a --source, or a plan's source.local_path, with a part of its repository git reads at mode 000 (the .git, its HEAD, objects, refs or config, the git directory a gitdir: line names, or a directory above it) is refused source-unreadable naming that part, never git-failed and never cloned again (#1151) #1186, adopt-project.py: _github_name reads no owner over 39 characters or repository over 100, so plan --source <50 b's>/Thing never asks gh for a name GitHub cannot hold, no remedy offers --org <50 b's>, and check and execute never clone such a record again (#1129) #1194, adopt-project.py: _pin_row_problem says a pin field whose value is not text as _read_as says what the plan's reader read, "records commit: as the number 7", where it spelled it as Python does, commit: 7 and True, so check and execute name it in the words the plan's other refusals use under the same plan-pin-malformed (#1114) #1198, adopt-project.py: check and execute read one list of a plan's problems against its source, in the order a person mends them (another repository, a refused name, a moved source, an unanswered entry), so check's first finding is execute's refusal for each pair (#1113) #1210, adopt-project.py: a refused command's REFUSED block spells each character a terminal acts on in the command, its directory and what it printed, as _first_said spells gh's, so --local-remote-dir no longer reaches the terminal raw there; a NOTE before NEXT: says when the source's path in its clone command holds a character a terminal does not show as itself; and rows pin six sites #1177's 148 left to chance (#1195) #1225, update-shape.py: a line with U+00A0 or U+3000 in front of shape: or files: is no block header, as it is none to the reader (#1220) #1231, adopt-project.py: the mount removes a path a case twin is at or under with git rm -f and writes the twins the root keeps back from the index, so on a disk that ignores case execute keeps a set the plan splits across repositories or drops (#1183) #1237 (87325bed2), update-shape.py: apply puts its REFUSED and ROLLED BACK report, and a failed push's account of the commit that stands, on stderr when nobody reads stdout any more, and a broken pipe on a copied line is rolled back and raised as itself, not called update-write-failed (#1218) #1241, repo_shape.py and adopt-project.py: a plain integer of more digits than this Python reads, or a decimal too large to be finite, is yaml-unparsable by its line, where it was a ValueError traceback or "the number inf", and _read_as says a float in digits with no exponent, where it said "the number 1e+300" (#1212) #1249, 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, adopt-project.py: check's compare for the mount's git rm skips the .gitattributes it leaves and reads its exit code, so the root's failing filter is plan-attributes-reencoded alone, and a refused compare is named in git's words (#1192) #1253, 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, adopt-project.py: each clone execute makes names its remote origin, whatever clone.defaultRemoteName says, so execute no longer ends in REFUSED mounting the legs ("No such remote 'origin'") after both legs are pushed (#1261) #1268, update-shape.py: prepare asks the shape pin's source_repository: once, with --upstream passed too, and refuses a path or a value that is no owner/repo in the pin's name; shape-doctor.py answers as it did (#1203) #1269, adopt-project.py: a source whose only remote is not named origin, as clone.defaultRemoteName names it, is read from that remote, so plan reads its organisation, default branch and branches where it refused adopt-no-org, took HEAD's branch as the default and asked --tracking-branch of the source's own branches, and one with several remotes and no origin names them and guesses none (#1276) #1280, adopt-project.py: plan-wrong-kind, plan-unsupported-mode, plan-bad-leg and plan-bad-id say a plan value that is not text as _read_as says what the plan's reader read, kind is the number 7 and declares leg a list of 2 item(s) where repr spelled it 7, True and ['a', 'b'], and a plan with no kind: or mode: line says absent where it said None (#1199) #1281, repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, update-shape.py and the root's validate-manifest.py: not-a-mapping and wrong-kind refusals name the file as shown_path does, so no U+202E, ESC or line feed is raw (#1279) #1284.

  8. The body and the PR record.

    • There is one Closes #1215 and no other closing keyword, and closingIssuesReferences is [1215].
    • The title is 244 characters.
    • The only non-ASCII character is the robot in the footer.
    • SonarCloud for PR 1294: quality gate OK, 0 open issues, 0.0% new duplication.
    • Re-read at 00:33Z: main is still 370a23c and the head is still 70747e4. CI checks: package, tests-git-bash and SonarCloud pass; tests was still pending.

State left. I made no GitHub writes and no commits; the predicted merge is an unreferenced commit-tree. rv1294-review and rv1294-mut are both detached at 70747e4, clean, and their sha256 values match mut-orig.sha256. No rv1294 process is running.

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

  • STATE.md
  • gates.summary
  • mutants.summary
  • pm.summary
  • merge-trees.log
  • logs/
  • probes/ (amb2-243.log, amb2-2471.log, ambm-*.log, fr2.log, scan-head.txt)

Lane: openreposhape-5 (openRepoShape-5)

…e third row's colon name is skipped on Windows as the second's is, and two comments name any work-tree entry, tracked or not, as what trips the revision (#1215)

rv1294's review of the pull request found the third row's colon name
(`refs/remotes/origin/<base>:contracts/shape-pin.yaml`) a bare value, where
the file's docstring says the second and third names are skipped on Windows:
on Windows that row would fail at the `mkdir` in `tracked()` instead of
skipping. It is now a `pytest.param` with `NOT_ON_WINDOWS`.

The comments at `_land`'s `diff-tree` and `_committed_pin` said git refuses
the argument where the root TRACKS a file of that name. Git checks the work
tree, so an untracked entry trips it too (probed on git 2.43.0 and 2.47.1);
they now say so, as `Source.commit_count`'s docstring does. Comments only.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Re-check of #1294 (#1215) for lane openRepoShape-5, by rv1294b (sonnet), at ce9fbee: READY

Summary. The one round commit ce9fbee6e does what R30 describes, and I measured every item. There is no MUST, SHOULD or NIT. Gates at the head pass on both gits (827 ids, 0 failed). tests in CI was still IN_PROGRESS at 01:02Z.

All folders and paths below are under L/work/rv1294b/, where L is /workspace/projects/.lane-worktrees/openRepoShape-5.

Nothing else changed (check 1)

  • git diff --stat 70747e4ab..ce9fbee6e lists two files, +9/-5: tests/test_update_shape_head_named_entries.py (+3/-1) and update-shape.py (+6/-4). The commit is one commit on 70747e4ab.
  • update-shape.py's ast.dump is equal before and after (Python 3.12.3, logs/ast-update-shape.log).
  • The test change is the mark alone. I stripped the one new pytest.param(<BinOp>, marks=NOT_ON_WINDOWS) back to its value, and the dump then equals 70747e4ab's (logs/ast-test-mark-only.log).
  • The update-shape hunk headers are _land and _committed_pin.

SHOULD 1, the mark (check 2)

  • A plugin printing each item's own skipif marks (plug/rv1294bmarks.py, logs/marks-head.log) shows:
    • At the head, both colon ids carry skipif(False, reason='Windows cannot give a file a colon in its name'). The is NOT_ON_WINDOWS.mark check is True for both, and one distinct mark object is seen. The control ids carry none.
    • At 70747e4ab, the third row's colon id has no skipif mark (logs/marks-70747e4ab.log). That is the mutant for this fix, and it is killed by the collection.
  • The module defines NOT_ON_WINDOWS once (line 47) and uses it at lines 94 and 111.
  • Simulation, since there is no Windows host: copies of the test file with os.name == "nt" replaced by True (probes/winsim/).
    • The head file skips both colon ids and passes the other 4 (logs/winsim3.log).
    • The 70747e4ab file skips only one (logs/winsim4-before.log).
    • This shows the pytest skip behaviour, not Windows behaviour.
  • The rows pass on this host: 6 passed in logs/g243-part-00.log, and 11 over both new files at the predicted merge (below).

NIT 5, the comments (check 3)

  • Raw git in a scratch repository with UNTRACKED entries named as each argument (probes/untracked.sh, logs/untracked-243.log, logs/untracked-2471.log), on git 2.43.0 and 2.47.1:
argv without -- exit with --
diff-tree ... -z HEAD 128 0
show HEAD:contracts/shape-pin.yaml 128 0
show refs/remotes/origin/main:contracts/shape-pin.yaml 128 0
rev-list --count HEAD 128 0
rev-list --count <tip id> 128 0
  • All ten exits are the same on both gits. Each refusal reads ambiguous argument ...: both revision and filename. With no such entry, every argv exits 0.
  • The new comment wording ("the root's work tree holds an entry of that name, tracked or not") is what was measured.

The body's claims (check 4)

  • 6 fail and 5 pass on main.
    • I swapped the two production files from 4552efa6a, the body's base, into rv1294-mut under the head's tests, over the 11 ids.
    • Result: 6 failed, 5 passed on git 2.43.0 and on 2.47.1 (logs/swap-243-a4552.log, logs/swap-2471-a4552.log).
    • The failing ids are the 6 named in the body, and the 5 controls pass.
    • On current main 370a23cd2 (its whole tree plus the head's two test files) the result is also 6 failed, 5 passed on both gits (logs/main-243-m1.log, logs/main-2471-m1.log).
    • Each swap was restored by sha256, and the tree was clean after every run.
    • One run is a harness artifact: swapping only main's two files from 370a23cd2 gave 8 failed, 3 passed (logs/swap-*-a370.log). Main's adopt-project.py imports BOOTSTRAP_COMMAND from shape_materialize.py, which main also changed. That is not a finding.
  • _land naming. def _land is at update-shape.py:5248 and cmd_apply calls it at 5371. The body's four cmd_apply mentions all say _land (which cmd_apply calls).
  • adopt-project.py: the mount removes a path a case twin is at or under with git rm -f and writes the twins the root keeps back from the index, so on a disk that ignores case execute keeps a set the plan splits across repositories or drops (#1183) #1237 merges clean. Its head is 87325bed2, and git merge-tree (2.47.1) gives rc=0.
  • Without git filter-repo. With git filter-repo not a git command on PATH, 2 passed and 3 skipped (logs/nofr-adopt-rows.log). The 3 skips are the execute ids, SKIPPED [3] tests/test_adopt_rev_list_dashdash.py:60: git filter-repo is not installed. The plan/check row's 2 ids pass. The body's quote of that skip reason uses single quotes where the real text has backticks, which is cosmetic only.
  • _take_held_seed. I read it against scaffold-project.py.
    • _held_seed accepts a remote commit only when its tree equals seed_tree(...), the tree built from the values.
    • The _take_held_seed docstring says the tree "is what was just written", and the reset -q <seed> follows a fetch of that commit.
    • So the body's NIT 4 reason is exact.
  • Argv echo.
    • A git-failed refusal from git_out now prints git rev-list --count HEAD -- in the refusal text (logs/argv-echo.log).
    • grep -F 'rev-list --count' tests README.md docs AGENTS.md at the head hits only tests/test_adopt_rev_list_dashdash.py, lines 5, 13, 69 and 113. All four are docstring lines that assert nothing (logs/pins-grep.log).

Gates (check 5).

  • Head ce9fbee6e, rv1294-review clean (dirty=0 in each START line).
  • I built the ids myself: 17 files, 827 ids, 9 parts of at most 100, with the new update-shape file and tests/test_update_shape_partial_rollback.py together in part 00. The 827 matches n1215c's count.
  • Runner L/tools/run10.sh, run names rv1294b-g<git>-NN, Python 3.12.3, -rs alone.
  • Files: both new test files, test_update_shape_partial_rollback, _apply_lines_and_headers, _merged_away, test_update_shape, _landing, _pr_base, _messages, _next_quoting, test_adopt_e2e, the hygiene five, and test_no_auto_maintenance.
git filter-repo parts ids passed skipped failed
2.43.0 (/usr/bin/git) 2.47.0 (--version prints a40bce548d2c) 9 827 825 2 0
2.47.1 (L/tools/git247/bin/git) 2.47.0 (same) 9 827 825 2 0
  • Skips, the same on both gits: SKIPPED [2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one.
  • Summary in gates.summary, logs in logs/g<git>-part-NN.log.

Merge-trees (check 6). Fetched at 00:52Z on git 2.47.1 (merge-trees.log). origin/main was 370a23cd2 and the PR head ce9fbee6e (ls-remote again at 01:02Z, unchanged).

The record (check 7).

  • The live body has one Closes #1215 (line 193), no other closing keyword, and closingIssuesReferences is [1215]. body-guard.py exits 0. The title is 244 characters and ASCII, and the only non-ASCII character in the body is the robot in the footer. The body was unchanged on re-read (30594 characters).
  • The Lane: line and footer are present, and the commits have no closing word except the first commit's Closes #1215.
  • The Sourcery section is present, last in the body, and ## Summary by Sourcery is not edited by hand in anything I saw. Its wording differs from the copy n1215c and rv1294 saw. I take that as Sourcery's regeneration after the push. A later body edit must not paste an old copy over it.
  • The PR is a draft on milestone v1.2.2. package, tests-git-bash and SonarCloud pass, and SonarCloud shows 0 new issues, 0.0% duplication and a quality gate that passes. tests is the one check not finished.

State left. I made no GitHub writes and no commits (the predicted merge is an unreferenced commit-tree). rv1294-review and rv1294-mut are both detached at ce9fbee6e and clean, with the production and test files' sha256 equal to the head's blobs. No process of mine is running. The scratch repositories and basetemps stay in probes/ and L/tools/bt/rv1294b-* (no rm). STATE.md is in L/work/rv1294b/.

Lane: openreposhape-5 (openRepoShape-5)

This was referenced Nov 10, 2026
@sonarqubecloud

Copy link
Copy Markdown

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