Repository navigation
adopt-project.py: check asks the split's own clone of the source in the work directory, so a clone this git will not make is adopt-git-unasked before any leg, and _resolved and _answers make their repositories as check's other throwaways do (#1083) - #1119
Conversation
…` of the source in the work directory, and `_resolved` and `_answers` make their repositories as #910's throwaways are made (#1083) Work in progress for #1083, stopped at a usage checkpoint. Built: `_split_clone_asked` (asked first in `_entry_findings`; a failure is `adopt-git-unasked`, which `execute` refuses before any leg); `_resolved` and `_answers` made with `_throwaway`/`_throwaway_made` in the work directory, no hooks, `--template=<empty>`, `-b`, the source's format, under `_inherited_env()`; `_throwaway_git`; `_submodule_findings` skips the url questions once one throwaway could not be asked. 16 rows added or changed in tests/test_adopt_check_throwaway_repos.py (45 rows: all pass on this head; 14 fail and 31 pass over 2a119f2's adopt-project.py). Left: the TMPDIR docstring in tests/test_adopt_reencoded_files.py, the gate, and the PR body. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e_checkout_writes_is_never_written_through` says the throwaway repository is made in the work directory, as #910 makes it, and no longer in `TMPDIR` (#1083) The last item of #1083's (c): the docstring still said the throwaway repository the link is asked in is in `TMPDIR`. Since #910 it is made in the work directory, which is as deep below `tmp_path` as the `TMPDIR` the row sets, so the row asks the same question and only its words change. (The issue's `adopt-project.py:466` names `_pin_row_problem` at 74c1770; this docstring is the one PR #1031's body named.) Gate on the merged tree (main 6da757c): see the pull request body. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's Guide
Sequence diagram for check rehearsing the source clonesequenceDiagram
participant Check
participant WorkDir
participant Git
participant Execute
participant Legs
Check->>WorkDir: _throwaway()
Check->>Git: clone --no-checkout --shared with FILE_PROTOCOL
alt clone succeeds
Check->>Git: _paths_git_refuses()
Check->>Git: _submodule_findings()
Check-->>Execute: findings and plan
Execute->>Legs: create and extract split legs
else clone fails
Git-->>Check: exit status and stderr
Check-->>Execute: adopt-git-unasked
Execute-->>Legs: refuse before creation
end
Sequence diagram for submodule URL questions in throwaway repositoriessequenceDiagram
participant Check
participant Throwaway
participant Git
participant Findings
Check->>Throwaway: _resolved(source)
Throwaway->>Git: _throwaway_git(configure origin and .gitmodules)
Throwaway->>Git: _throwaway_git(hash-object and update-index)
Throwaway->>Git: git submodule init
Git-->>Throwaway: resolved URL or failure
Check->>Throwaway: _answers(urls, source)
Throwaway->>Git: git ls-remote --get-url
Throwaway->>Git: git ls-remote for local URLs
alt throwaway creation or Git query fails
Git-->>Findings: _Unasked as adopt-git-unasked
Findings-->>Check: stop later URL questions
else questions succeed
Git-->>Findings: URL findings and notes
end
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-10-21T14:36:09Z) r1119, lane 1's independent reviewer (not the writer), reviewed #1119 at r1119 review of PR #1119 at
|
| # | Claim | Measured |
|---|---|---|
| 1 | init.defaultBranch = a..b (global config): before plan ok / execute exit 2 at the spec leg with both legs made; after adopt-git-unasked / REFUSED adopt-git-unasked, remotes/ absent |
true. The head finding ends "the split's git clone of the source exited 128 there: fatal: invalid branch name: init.defaultBranch = a..b" |
| 2 | GIT_DEFAULT_HASH=bogus: before plan ok / "a git or gh command failed" with Northwind-spec.git left; after FINDING / REFUSED, remotes/ absent |
true. Base left ['Northwind-spec.git'] |
| 3 | kept submodule with a..b: before exit 2 at git init -q <TMPDIR>/openreposhape-url-*; after FINDING / REFUSED |
true. Base said git init -q …/tmpx/openreposhape-url-vnouwe9o. On git 2.43 the head finding is the clone's, not _resolved's |
| 4 | kept submodule with GIT_DEFAULT_HASH=bogus |
true |
| 5 | counting hook in the global config, kept submodule: 2 runs before, 0 after; the rest of the output byte for byte the same | true. Base ['post-index-change', 'post-index-change'], head []. Head's output is the same with and without the hook, and base and head differ only in the plan's path |
| 6 | no submodule, same hook: 0 / 0 | true |
| 7 | protocol.file.allow = never: clone exits 128 without FILE_PROTOCOL, 0 with it |
true (transport 'file' not allowed) |
| 8 | a shallow source and a --shared-clone source clone with exit 0 |
true, but see NIT-1 |
| 9 | 45 rows, 45 passed on head | true (45 passed in 93 s) |
| 10 | 14 failed / 31 passed over the base tool | true over both 16f9a0e's and 6da757c's adopt-project.py, the same 14 rows |
| 11 | 2a119f2's adopt-project.py is unchanged through 16f9a0e |
true (empty diffstat) |
| 12 | Known limit: M6/M14, the _inherited_env() gap |
the gap is true (SHOULD-2). Calling M6 only a fidelity limit is false (SHOULD-1) |
| 13 | Behaviour change: an unwritable work dir asks no url question | true. "Its NOTE still says what went unasked" is partly false (NIT-2) |
| 14 | Mutants: 13 of 16 killed | unverified: no mutant was run over the tests |
| 15 | Gate: 3,050 passed, 5 skipped | unverified: stopped before the first batch ended |
| 16 | M15 is equivalent | unverified |
Cost of the new clone, against base (base makes none)
| Source | Clone time | Disk | Whole check, base vs head |
|---|---|---|---|
| openRepoShape (516 commits, 179 refs) | 0.06 s | 0.1 MiB | not timed |
| 100k files, 115 MiB pack, 20,001 refs | 0.8 s and 1.6 s | 1.2 MiB | 55.7–61.2 s vs 59.8–82.2 s (+4–8 s typical; 82.2 s is one noisy run) |
| the same, shallow | 7.3 s and 10.2 s | 117 MiB, a full copy | 59.9 s vs 65.2 s |
The load average was about 90 throughout, so the whole-check times are noisy.
Mutants: 0 run over the tests
- I designed 16 mutants. None of them was run over the tests before the checkpoint.
- M6 was only probed by hand (SHOULD-1); I did not run it over the test file.
Merge with main
- Main moved to
7cd66902(scaffold-project.py: the--reuse-empty-repohelp, its docs and each remedy that sends a person into that re-run say ssh, a credential helper or askpass can still make it wait, and how to keep it from waiting (#915) #1101:scaffold-project.py, README.md, docs/cli.md, docs/handbook.html,tests/test_scaffold_reuse_seeded_legs.py,tests/readme-cap.d/915.txt). It overlaps none of this PR's files. git merge-tree --write-tree bdcf9952 7cd66902is clean (exit 0, treeaad41027).- The scratch merge commit is
5211aece, checked out detached at/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1119/merged. - No gate counts. The gate was stopped at the checkpoint before its first batch finished.
Not finished (for a fresh reviewer)
- Mutant runs. Run them in
$S/base, with a basetemp separate from the gate's. Run each overtests/test_adopt_check_throwaway_repos.py; the url mutants also overtest_adopt_relative_submodule_url.pyandtest_adopt_submodule_url_answers.py, and R21 also overtest_adopt_c_locale_bytes.py. Use-x, and restore and sha-check after each. R6, R7, R8, R14, R15 and R21 needcount=1replacements: patch_resolution_of, the ls-remote calls and the second_resolved(…)call by their own lines.- R1: no removal in
_split_clone_asked. - R2: no
--shared. - R3: the clone asked after
_paths_git_refuses. - R4:
_submodule_findingsdropsmoved/noteson an_Unasked. - R5:
_entry_findingsignores the returnedunasked. - R6:
_throwaway_gitwithout.strip(). - R7:
_resolution_of(scratch…). - R8:
_answers' ls-remote withcwd=scratch, which I expect survives. - R11 and R17:
_Unasked(scratch…). - R12: a clone failure as
unmade=True. - R14: the second
_resolvedwithoutsource. - R15: no
ON_THIS_DISK_ONLY. - R21:
basewithout_path_argument. - R26 and R27: no removal in
_resolved/_answers. - Also run M6 itself over the tests, which I expect survives.
- R1: no removal in
- The gate on
$S/merged, using$S/gate.sh: the PR's file;test_shape_doctor.pyin 4 shards ($S/doctor-shard{0..3}.txt, 112 ids each);test_scaffold_leg_path_collisions.py; andtest_adopt_c_locale_bytes.py,test_adopt_clone_claimed.py,test_adopt_relative_submodule_url.py,test_adopt_submodule_url_answers.py,test_adopt_shape_pin_and_work_dir.py,test_adopt_work_dir_refusal.py,test_adopt_check_plan_name.pyandtest_adopt_reencoded_files.py. - Safety probes not run:
core.fsmonitor(global, and in the source's config);- a smudge or process filter;
submodule.recurse=true;- an
init.templateDirholding hooks and aconfig; - a partial-clone or promisor source, against git 2.43's CVE-2024-32004 class. That class is already reachable through the reads of the source
checkmakes, andexecuteclones the source anyway.
- Cleanup not measured: on KeyboardInterrupt (by reading, the
try/finallycovers it), and two concurrentcheckruns in one work directory (by reading,mkdtempnames are unique).
Scratch: /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1119/ holds probe.py, cost.py, bigsource.py, timecheck.py, gate.sh and probes/.
Lane: openreposhape-1 (openRepoShape-1)
… parent git hands it, as execute's does, and no variable naming another repository; _resolved and _answers read their environment before their throwaway (#1083) r1119's fix round on #1119. SHOULD-2: `_split_clone_asked` makes the clone in `_split_clone_env()`: `_inherited_env()` plus `GIT_CONFIG_PARAMETERS` where it is set. Measured on bdcf995 with git 2.43, `GIT_CONFIG_PARAMETERS="'init.defaultbranch'='a..b'"` gave `plan ok` from `check`, and `execute` made both legs and stopped extracting the spec leg. Now `check` says `adopt-git-unasked` and `execute` refuses before any leg, as for the setting in a config file or in `GIT_CONFIG_COUNT`. A parent's `-c core.hooksPath` runs no hook: the clone's own `-c` comes after it. SHOULD-1: a row runs `check` under `GIT_OBJECT_DIRECTORY` naming the source's own objects and holds the source's object directory unchanged. It kills M6: in the whole environment the clone wrote `objects/info/alternates` into the source. A row under `GIT_DIR` naming the source, whose own config rewrites the on-disk url, kills M14 (`_answers` in the whole environment) and holds the source's config unchanged. NIT-1: the docstring says git ignores `--shared` for a shallow source, so that clone is a full copy, a cost `execute`'s own clones pay too. NIT-2: the unmade-work-directory NOTE says it asked nothing about the urls of the submodules the plan keeps either. NIT-3: `_resolved`, `_answers` and the clone read their environment before they make their throwaway, so a raise there leaves none behind. tests/test_adopt_check_throwaway_repos.py: 63 rows (45 before), 18 new. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-21T16:52:08Z) r1119b, lane 1's independent reviewer (not the writer), re-checked #1119 at
A fix round follows. The review follows in full. Verdict: NOT READY. Two MUSTs. Both have small fixes, and I proved each fix on a scratch copy. All five round-1 findings are resolved. Every check the brief listed was run; this report is complete, not PARTIAL. Nothing was written to GitHub, committed or pushed. My worktree Merge with main (
|
| Batch | Result |
|---|---|
test_adopt_check_throwaway_repos.py |
63 passed |
test_no_auto_maintenance.py |
1 failed, 57 passed, 2 skipped (MUST-1) |
test_scaffold_leg_path_collisions.py |
104 passed |
test_shape_doctor.py, 4 shards of 112 |
445 passed, 3 skipped |
c_locale_bytes, clone_claimed, relative_submodule_url, shape_pin_and_work_dir, submodule_url_answers, work_dir_refusal |
188 passed |
test_adopt_root_symlinks.py (it names adopt-work-dir-unwritable) |
1 failed, 204 passed, 3 skipped (MUST-2) |
Round-1 findings
- SHOULD-1: resolved.
- M6 is killed by
test_the_clone_writes_nothing_into_an_object_directory_around_it. - M14 and M14r are both killed by
test_a_repository_around_check_rewrites_no_url_it_asks. - Each was my own run on head. The body now calls this a safety property.
- M6 is killed by
- SHOULD-2: resolved, and sound.
- Every form a parent hands down reaches the clone. The new quoted form, the old
'key=value'form and a real parentgit -c init.defaultBranch=a..b ck(a shell alias) each giveadopt-git-unaskedon head and merged. Each gaveplan okatbdcf9952. - Why the
GIT_CONFIG_COUNTform already reached the clone atbdcf9952:_inherited_env()(:11418) re-emits the COUNT entries, dropping onlyattr.tree. It dropsGIT_CONFIG_PARAMETERSwhole (_NOT_INHERITED,:11411), because its quoted form is not parsed. Measured: baseplan ok;bdcf9952and headadopt-git-unasked. - Consistency: both forms now reach the clone, as both reach
execute's.attr.treeis filtered from the COUNT form but not from PARAMETERS. That is harmless for a clone with no checkout: a parentattr.tree=bogusstill givesplan ok. The same gap in adopt-project.pycheck's throwaway repositories: a SHA-256 source, a tree path git refuses, aTMPDIRon another disk and this machine's hooks change the answers #910's throwaways is adopt-project.py: #910's throwaways leave out a parent's -c settings and GIT_ATTR_SOURCE, so check says plan ok where execute stops after both legs, and three read the environment after making the throwaway (follow-up of #1083) #1136. - The clone's own
-c core.hooksPathstill wins: 0 hook runs with it, 4reference-transactionruns without it. A1 and A12, which put its setting before the parent's, are killed by thegit-config-parametershook row. - Nothing writes outside the throwaway: see probes 13 and 14 below.
- Every form a parent hands down reaches the clone. The new quoted form, the old
- NIT-1: resolved. The docstring (
:11222-11227) and the body say it. Measured: a-q --sharedclone of a shallow source prints nothing and makes noalternates. - NIT-2: resolved. Head's NOTE now ends "nor about the urls of the submodules the plan keeps";
bdcf9952's did not. - NIT-3: resolved. The environment is read first at
:7710,:7919and:11231. Undoing each one (N4r, N5a, N6c) is killed by its environment-read row.
Safety probes
Trees: base 823624d9 (before the PR), head, main 4ed7c7bf, merged. "Runs" counts programs the probe's own logging scripts recorded during check.
| # | Probe | Result | Writes outside the throwaway |
|---|---|---|---|
| 1 | core.fsmonitor program, global config |
8 runs on every tree, from _resolved's update-index. On head they run in the work dir's openreposhape-url-* (SHOULD-A) |
none |
| 2 | core.fsmonitor in the source's config |
0 | none |
| 3 | smudge and clean filter, global | runs only in #910/#654's -attributes-/-reencoded- throwaways, which rehearse a checkout by design; none in the clone, -url- or -ask-. The counts (28/30/24/30) differ only in clean inside -reencoded-, code this PR does not touch; I did not re-run to prove it is timing |
none |
| 4 | process filter, global | 2 runs on every tree, same throwaways as row 3 | none |
| 5 | filter in the source's config | 0 | none |
| 6 | submodule.recurse=true |
0, findings unchanged | none |
| 7 | init.templateDir with hooks and a config (fsmonitor, hooksPath) |
base and main 10 runs; head and merged 0 (an improvement) | none |
| 8 | template config naming remote.origin.uploadpack |
0 | none |
| 9 | partial clone, history blobs missing | 0 | none |
| 10 | partial clone, HEAD blobs missing (re-made after plan) |
the source's own uploadpack program runs 5 times, and a promisor pack is written into the source, identically on all four trees. It comes from check's reads of the source, not from this PR (NIT-D) |
the source's objects/pack (all trees) |
| 11 | shallow source, global uploadpack.packObjectsHook |
head and merged 1 run, base and main 0 (NIT-C) | none |
| 12 | shallow source, hook and fsmonitor in the source's config | 0 | none |
| 13 | GIT_ALTERNATE_OBJECT_DIRECTORIES and GIT_SHALLOW_FILE naming a victim repository; GIT_NAMESPACE; GIT_REPLACE_REF_BASE |
plan ok |
none |
| 14 | a parent -c of hooksPath, fsmonitor, core.worktree=<victim>, commit-graph, reflogs, auto gc and maintenance, sharedRepository |
base 10 runs; bdcf9952, head and merged 0 |
none |
| 15 | SIGINT, to the process group and to Python alone, paused in the clone and in _resolved |
work dir empty in 8 of 8 (head and merged). Exit -2 with a traceback, which predates this PR | none |
| 16 | two concurrent check runs, one work dir |
they overlapped (2 throwaways at once); both match a solo run; work dir empty after | none |
New findings
MUST-1: head fails a gate test (tests/test_adopt_check_throwaway_repos.py:1243)
test_the_clone_takes_a_parent_gits_settings_and_no_other_repositorycallsappended_to_command_scope({}, …).- That fails
tests/test_no_auto_maintenance.py::test_no_test_sets_a_git_config_count_of_its_own. It failed in lane 1's G5 on head and in my run on the merged tree. - Fix: replace that block with:
for name, value in appended_to_command_scope(
os.environ, [("attr.tree", "HEAD"), ("test.heldone", "one")]).items():
monkeypatch.setenv(name, value)
env = adopter._split_clone_env()
assert env["GIT_CONFIG_PARAMETERS"] == parameters
assert [env.get(name) for name in named] == [None] * len(named)
scoped = [(env[f"GIT_CONFIG_KEY_{n}"], env[f"GIT_CONFIG_VALUE_{n}"])
for n in range(int(env["GIT_CONFIG_COUNT"]))]
assert scoped[-1] == ("test.heldone", "one")
assert "attr.tree" not in [key.lower() for key, _ in scoped]- Proven: with it, the row and the scan both pass.
- It still tests what it claims: the row still fails under A5 (
GIT_ATTR_SOURCEhanded back), A15 (GIT_CONFIG_PARAMETERSalways set) and a mutant that keepsattr.tree.
MUST-2: the merge with main fails a test that main added (main's tests/test_adopt_root_symlinks.py:2070)
test_check_says_where_git_would_not_make_the_ref_probe(adopt-project.py:checknames what the clone's.githolds as git keeps refs, and asks a pin's file through the links the root keeps, so a link into.gitor onto a pin's file is refused before any leg or adopted with the copy beside it (#904) #1091) setsGIT_DEFAULT_HASH=bogus(:2077) and expectscheckto exit 0.- This PR's clone (
adopt-project.py:9427) makes that settingadopt-git-unasked, socheckexits 1. - Measured: the row passes on main and fails on the merged tree. merge-tree is clean, so a plain update-branch would land a red main.
- Fix: merge
origin/main, then make only the ref probe'sgit initfail, and update that row's docstring:- in place of the
setenv("GIT_DEFAULT_HASH", "bogus")line, add a stubgitfirst on PATH, skipped on Windows; - the stub prints
fatal: unknown hash algorithm 'bogus'and exits 128 when its arguments matchinit*refs-probe-*; - otherwise it runs
exec "$REAL_GIT" "$@".
- in place of the
- Proven: with that change the row passes on the merged tree.
- This adds
tests/test_adopt_root_symlinks.pyto the PR's files.
SHOULD-A: a core.fsmonitor program still runs in _resolved's throwaways (adopt-project.py:11118)
- adopt-project.py
check's throwaway repositories: a SHA-256 source, a tree path git refuses, aTMPDIRon another disk and this machine's hooks change the answers #910's throwaways pass-c core.fsmonitor=false(:11823,:12106): "No fsmonitor daemon is started for a repository that is about to go". hooks_offhere does not, though the body says these repositories are "made as adopt-project.pycheck's throwaway repositories: a SHA-256 source, a tree path git refuses, aTMPDIRon another disk and this machine's hooks change the answers #910 makes the others".- A global fsmonitor program ran 8 times on head. It ran there before this PR too, in
TMPDIR. - By reading, with
core.fsmonitor=trueon macOS or Windows this starts a daemon per throwaway. I could not measure that on Linux. - Fix:
hooks_off = ["-c", f"core.hooksPath={empty}", "-c", "core.fsmonitor=false"], plus a row with a counting fsmonitor. - Proven: 8 runs → 0, and the PR's file plus
test_adopt_submodule_url_answers.pypass (85 passed). - This can go to adopt-project.py: #910's throwaways leave out a parent's -c settings and GIT_ATTR_SOURCE, so check says plan ok where execute stops after both legs, and three read the environment after making the throwaway (follow-up of #1083) #1136 if lane 1 prefers.
NIT-A: R8 survives because no row puts --work-dir inside a repository (:7929; a row beside :1283)
- With a work dir inside the source and the source's config rewriting the url:
- head gives
[plan-submodule-relative-url]; - R8 adds a false
plan-submodule-url-answers-nothing.
- head gives
- Fix: add that row.
NIT-B: A18 survives (docstring at :7911-7913)
- The docstring says
_answersleaves outGIT_CONFIG_PARAMETERS; no row pins it. - Fix: add a
git-config-parametersurl-rewrite variant of the:1283row.
NIT-C: a shallow source makes the clone run uploadpack.packObjectsHook from protected config (probe 11)
execute's clone runs it too, and turning it off would cost fidelity.- Fix: one sentence in
_split_clone_asked's docstring or under Known limits.
NIT-D (predates this PR; for a follow-up issue): probe 10. check's own reads of a partial source fetch from its promisor, running the source's uploadpack program and writing into the source, the same on every tree.
NIT-E: the body's Gate section is stale. It says 8 batches and 0 failed. It should now carry lane 1's G5 failure and MUST-2's merged-tree result.
Mutants: 31 killed of 36 run
- r1119's list, 14 of 17 killed. Run: R1–R8, R11, R12, R14, R15, R17, R17b, R21, R26, R27. R9, R10, R13, R16, R18–R20 and R22–R25 are described nowhere in round 1, so I did not run them.
- R2 (no
--shared) survives: only cost changes. - R3 (clone after
_paths_git_refuses) survives: nearly equivalent, since the clone's finding wins either way. - R8 survives, and is a real gap: NIT-A.
- R2 (no
- SHOULD-1's three, 3 of 3 killed: M6, M14, M14r.
- My own, 14 of 16 killed: A1, A2b, A5, A6, A6b, A11, A12, A15, A22, A23, A24, N4r, N5a, N6c.
- A17 (
_resolvedtakes the parent's-c) survives. That is not a defect: the throwaway's own hooks setting still wins. - A18 survives: NIT-B.
- An over-broad A2 was replaced by A2b and is not counted.
- A17 (
Body claims
| Claim | Measured |
|---|---|
Closes #1083 is the only closing keyword; #1032 is "Covers", #1136 "filed as" |
true (regex scan; GitHub lists only #1083 as closed) |
| Known limits names #1136 | true (#1136 is open and holds all three items) |
| Title within the 250 cap | true, 248 |
| 63 rows; 35 new = 7+8+2+18 | true |
Over bdcf9952's tool: 9 failed / 54 passed, and the 9 named |
true, exactly those 9 |
Over 16f9a0e8's tool: 30 failed / 33 passed; 15 of r1119's 18 rows fail, 3 pass |
true |
GIT_CONFIG_PARAMETERS row: before plan ok, after adopt-git-unasked |
true for check. execute was not run by hand; its rows pass on head and fail over bdcf9952's tool |
Without its own -c, the parent's hooks ran reference-transaction 4 times |
true (4) |
attr.tree=bogus still clones |
true |
GIT_ALTERNATE_OBJECT_DIRECTORIES and GIT_SHALLOW_FILE write nothing |
true (I did not try a non-empty shallow file) |
| (b) "no hook" | partly false: the fsmonitor program runs (SHOULD-A) |
| 11 killed this round; 24 of 25 across both rounds | the arithmetic holds. M6, M14, M14r and N4–N6 reproduced; the first round's 13 were not re-run |
| Gate: 1,192 passed, 0 failed | the sum is true but stale: G5 has 1 failure (NIT-E) |
Probe scripts and logs are in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1119/r2/ (probe2.py, probe_s2.py, probe_intr.py, probes/). The mutant definitions and results are in mutants.py and mut/summary.txt there, and the gate logs in gate/.
Lane: openreposhape-1 (openRepoShape-1)
….fsmonitor=false`, and the rows r1119b named pass on main: the clone-env row appends to the environment's count, and the ref-probe row fails only the probe's `git init` r1119b's second review of #1119, after the merge of main 038dafb (#1091, #1111): - MUST-1: `test_the_clone_takes_a_parent_gits_settings_and_no_other_repository` built its `GIT_CONFIG_COUNT` from `{}`, which `test_no_test_sets_a_git_config_count_of_its_own` refuses. It now appends to `os.environ`, and asserts the last entry kept and no `attr.tree`. - MUST-2: main's `test_check_says_where_git_would_not_make_the_ref_probe` set `GIT_DEFAULT_HASH=bogus`, which now stops the split's own clone first (`adopt-git-unasked`, exit 1). A stub `git` first on PATH fails only `git init` of a `refs-probe-*` directory, with git's words; skipped on Windows. - SHOULD-A: `_throwaway_made` returns `-c core.fsmonitor=false` beside `core.hooksPath`, as the rehearsal and `_stored_as_written` give theirs. A counting fsmonitor program ran 8 times in `_resolved`'s two repositories in one `check`; now 0, in the global config and in `GIT_CONFIG_COUNT`. New rows: that one and its control. - NIT-A: a row with `--work-dir` inside the source, whose config rewrites the url (kills R8). - NIT-B: a row with that rewrite handed down by a parent git in `GIT_CONFIG_PARAMETERS`, which `_answers` leaves out (kills A18). - NIT-C: `_split_clone_asked`'s docstring says a shallow source makes the clone run the `uploadpack.packObjectsHook` of protected config, as `execute`'s clones do. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inside the source also rewrites the url to a network, so a `--get-url` asked around the throwaway, which names it in a NOTE as not asked, fails it f1119c's mutant R8a, which moved only `_answers`' `git ls-remote --get-url` out of its throwaway repository into the throwaway's own directory, survived the row that kills r1119b's R8: the expansion there only decides between the disk and a network, and the url itself was still asked in the throwaway. A source config that rewrites the on-disk url to `https://f1119c.invalid/Thing` makes R8a print the NOTE of a url on a network it did not ask; on this head `check` prints none, inside the source or outside it. Now killed by the row's `a-network` case. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r system config as where the `uploadpack.packObjectsHook` a shallow source's clone runs comes from, since a parent's `-c` did not reach it cea2553's NIT-C sentence said "protected config ... or a `-c`". f1119c measured it on git 2.43, with a shallow source and a counting hook: - in the global config, 1 run in `check` and 1 in a clone made as `_extract_leg` makes it; - in the source's own config, 0 and 0; - in `GIT_CONFIG_PARAMETERS`, as a parent `git -c` hands it down, 0 and 0. So the `-c` is dropped. The commit message of cea2553 says "of protected config" for the same sentence; this is the measured form. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-21T22:23:41Z) r1119c, lane 1's independent re-check of round 3 at
The full re-checkVerdict: READY. I found no MUST and no SHOULD. Every item r1119b raised is resolved. Nothing was committed, pushed or written to GitHub. My worktree Checks1. Main. 2. MUST-1: resolved.
3. MUST-2: resolved.
4. SHOULD-A: resolved.
5. NIT-A, NIT-B, NIT-C: resolved.
6. Gate on head. I ran at most 3 pytest calls at once, each under 5 minutes, in the probe environment. Result: 931 passed, 0 failed, 8 skipped.
7. Body.
FindingsNo MUST and no SHOULD. The NITs are optional and none blocks landing.
Scratch and logs are in Lane: openreposhape-1 (openRepoShape-1) |
…nd execute a default branch git will not take share one helper each, so SonarCloud's new-code duplication on #1119 falls under its 3% gate (#1083) The parent-git rows and the global-config rows repeated one body each; each body is now a module-level helper that takes the function building the environment, and no row asserts or sets up anything else than before. All test ids keep their names. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s/test_adopt_check_throwaway_repos.py Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-23T18:08:24Z) Lane 1's delta check of #1119 since its READY head
Lane: openreposhape-1 (openRepoShape-1) |
…eps at a pin's file reached through a kept link, and holds that `check` renames the path the walk reaches, not the file kept in it, so rv1091's R6 no longer survives (#1116) (#1140) One row that holds the remedy `check` gives where a kept link leads a pin's walk to a directory, so that rv1091's mutant R6 (NIT 4 of its review of #1091, issuecomment-6316443939) is killed. This is a test-only change: `tests/test_adopt_root_symlinks.py` gains one row, and no production file, refusal code or sentence changes. It is a follow-up to #1091 (#904), so Brett Heap's ruling of 2026-10-21T10:32:04Z left it out of v1.2.0, and it is on the v1.2.1 milestone. - #1091 landed as `4ed7c7bf` and what it ships is right; only this row was missing. - `_kept_pin_finding` words a kept pin's finding through a kept link with `_through_the_kept_link(link, reached, entry)`. Its remedy says to send the link to a leg or `drop` it, "or rename `<reached>` in the source and re-run `plan`", where `reached` is the file the root walk reaches. - R6 hands that "rename" the kept row's own path (`path`) in place of `reached` (line 778: `link, reached, entry` becomes `link, path, entry`). - No row put a directory at a pin's reached file (rv1091's NIT 4), so in each of them `path` and `reached` are the same string and R6 passed all of them. With R6 applied alone, the two adopt-root files give `1 failed, 326 passed, 3 skipped`, and the one failure is the new row, so main's 326 other rows do not notice it. `test_a_directory_at_a_pins_file_through_a_link_names_what_it_reaches`, at `tests/test_adopt_root_symlinks.py:1173`, 30 added lines, in the file's style and on its helpers (`planned`, `declare`, `check`, `held_pins`, `symlink_findings`, `refused_by`, `execute_args`, `no_leg_was_created`, `ticked`): - the plan keeps `contracts -> governance`, keeps `governance/`, and puts a directory at `governance/openglass-pin.yaml` holding `notes.md`; a pin is declared on `openGlass`; - `check` exits 1, with no symlink finding; - its one `plan-root-holds-pin` line names the file `governance/openglass-pin.yaml/notes.md` kept under `governance/`, inside `contracts/openglass-pin.yaml` through the symlink `contracts` to `governance`, and its remedy renames `governance/openglass-pin.yaml`, the path the walk reaches, not the file kept in it; - `execute` refuses with `plan-root-holds-pin` and the same line before any leg. No assert joins conditions with `and` or `or`, the added lines are at most 76 wide (the file's widest is 80) and ASCII. The docstring says that before #1091 `check` passed this plan, measured on main `823624d9`. R6 alone, in a scratch tree at this head: `adopt-project.py` sha256 `0c1c1f3d7abf41136a61d7d4651f13fef5d3642a3162c024e921429b88bd7c1f` before, `9a921c6a9261234a68ef930e478421dca2d7135ad8674b357f2f55f46f773068` with R6, and restored to the first (checked), tree clean. | Where | Result | |---|---| | the row, with R6 | `1 failed, 208 deselected`: `assert held_pins(checked) == [line]` at `:1196`, the finding reading "rename `governance/openglass-pin.yaml/notes.md`" | | both adopt-root files, with R6, git 2.43 | `1 failed, 326 passed, 3 skipped`; the failed id is the new row alone | | the row, on `823624d9`'s `adopt-project.py` (before #1091) | `1 failed, 208 deselected`: `assert 0 == 1`, `check` said `plan ok` | All through `run9.sh` (stub `gh`, bogus token, `GIT_SSH_COMMAND=false`, no global git config), at head `2b6db57a3`: | Git | Files | Result | END | |---|---|---|---| | 2.43.0 | `tests/test_adopt_root_symlinks.py` and `tests/test_adopt_root_shape_paths.py` | 327 passed, 3 skipped (the reftable rows at 1956, 2011 and 2562) | 2026-10-21T16:21:00Z rc=0 | | 2.43.0 | `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py` | 170 passed | 2026-10-21T16:23:02Z rc=0 | | 2.47.1 (`git version 2.47.1`) | `tests/test_adopt_root_symlinks.py` | 209 passed, no skips | 2026-10-21T16:31:33Z rc=0 | The 2.43 count is 327 with the row, which is the 326 other rows plus it. `tests/test_agents_md_codes.py` passes, as no refusal code changed. - `origin/main` is `4ed7c7bf`, unmoved when fetched at the start, mid-way and before the push. `git merge-tree --write-tree origin/main HEAD` is clean, tree `d675b07f29edb9a061c3bc542dc2a76334d1e1a0`, the head's own tree: the branch differs from main by this one file, 30 insertions. - None of the 22 open PRs touched `tests/test_adopt_root_symlinks.py` when this was written. - **NIT round 1, 2026-10-22:** - origin/main `038dafba9dbedd9405b316cc749a04dffe02bb36` (#1111) is merged in as `e7623ed98`, cleanly, with tree `8a5cf7b1`, as rv1140 predicted. - The head `94712c3bc076142c16e48af70c218e6e958bd005` merges with main at its own tree, `f8faecc8cbcefbb87453f28647baf7a636ee1825`. - Of the 28 open PRs, #1119 (head `bba168bd`) now edits `tests/test_adopt_root_symlinks.py`. Its merge-tree with this head is clean, tree `81a0397437484f52b52789a86a2b5da43f712dae`. - As a check on the nearest neighbour, #1118 (`c60066cbe`, which edits `tests/test_adopt_root_shape_paths.py` and was cut at `6da757cb`) conflicts in that file with `origin/main` alone, and with this branch the same: that is #1118's, not this row's. This round is one merge of main and one new commit, after rv1140's review: READY at `2b6db57a`, with NITs (#1140 issuecomment-6341111415). - **The row now keeps two files** under `governance/openglass-pin.yaml/`: `README.md` and `notes.md`. - Git's order puts `README.md` first, so the one `plan-root-holds-pin` line names `governance/openglass-pin.yaml/README.md`. The sections above describe the first commit's one-file row. - This kills rv1140's mutant M3, which changes `rows[0]` to `rows[-1]` at `adopt-project.py:770`. M3 survived the one-file row. - The docstring names the sort order. - **Mutants, the row alone,** each applied alone and restored with its sha256 checked: - M3: killed (it survived before); - R6: still killed; - rv1140's M1, M2 and M4 to M7: all killed. - **Runs at `94712c3bc`:** - git 2.43.0, `tests/test_adopt_root_symlinks.py` and `tests/test_path_guard_hygiene.py`: **255 passed, 3 skipped**. The skips are the reftable rows. - git 2.47.1, the same files: **258 passed**. Review: lane 5's independent re-check, by a reviewer that wrote none of #1140: READY at `94712c3b`, the head that lands (issuecomment-6344746584), with no MUST, SHOULD or NIT. It checked the delta from `2b6db57a`, which rv1140 passed (issuecomment-6341111415). CI (Brett Heap's ruling 27): H0 is the head 94712c3 itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 038dafb; and the local gate passed on its predicted squash tree 19f483e over main dea964a: the adopt_root_symlinks, repo_hygiene, agents_md_codes, cli_reference sets, 377 passed, 3 skipped. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green". Closes #1116 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 17 hours and 2 minutes by commenting |
…fig, step 1 of 3 to restack #1169 onto main after #1119's squash One conflict, in the module docstring of tests/test_adopt_check_throwaway_repos.py: #1119 added its #1033 paragraph and #1136 added its own at the same place. Both stay, #1033 first. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… step 2 of 3 to restack #1169 onto main after #1119's squash Two conflicts, adopt-project.py and tests/test_adopt_check_throwaway_repos.py, both #1119's files; both took this branch's side with git checkout --ours. git diff 9b85644 b7edd88 is empty over the whole tree, so the merge changes no file: its tree is the tree of the commit before it. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…py` where GNU make at the root reads a kept `GNUmakefile` or `makefile`, and its NOTE names that file and what it takes for `make bootstrap` to run there (#1047) (#1121) GNU make reads the first it finds of `GNUmakefile`, `makefile` and `Makefile`, and only that one. Where the source keeps `GNUmakefile` or `makefile` in the root, `make bootstrap` at the root of the clone reads the source's file, not the shape's `Makefile`, and exits 2 with "No rule to make target 'bootstrap'". `execute`'s NEXT printed it there anyway. This PR also covers the third box of #1048 (see #1048): the `_next_steps` NOTE that said the shape's targets "arrive only once the follow-up merges the two", which is untrue where GNU make reads a kept `GNUmakefile` or `makefile`. #1105 covers that issue's other two boxes, and leaves this one to this PR. ## Measured GNU Make 4.3 on Linux, a disk that keeps case. One directory per set of files, each file with a `which` target: | files | `make which` reads | `make -n bootstrap` | |---|---|---| | `GNUmakefile` | `GNUmakefile` | exit 2, no rule | | `makefile` | `makefile` | exit 2 | | `Makefile` (with a `bootstrap` rule) | `Makefile` | exit 0 | | `GNUmakefile` + `makefile` | `GNUmakefile` | exit 2 | | `GNUmakefile` + `Makefile` | `GNUmakefile` | exit 2 | | `makefile` + `Makefile` | `makefile` | exit 2 | | all three | `GNUmakefile` | exit 2 | A `GNUmakefile` that holds `include Makefile` runs the shape's rule (exit 0), and so does `make -f Makefile bootstrap`. What NEXT printed on main, from a real `plan`, `check` and `execute --local-remote-dir`, with the printed command run in a recursive clone of the split ("S" is the source's own `scripts/bootstrap.py`, kept with `scripts/`): | the root keeps | NEXT on main | in the clone | |---|---|---| | none | `make bootstrap` | exit 0 | | `Makefile` | `python3 scripts/bootstrap.py`, #480's NOTE | exit 0 | | `makefile`, `GNUmakefile`, or both | `make bootstrap` | **exit 2, no rule** | | `Makefile` + `makefile`, `Makefile` + `GNUmakefile`, all three | `python3 scripts/bootstrap.py`, and a NOTE saying the targets arrive once the two `Makefile`s are merged | the command exits 0; `make bootstrap` exits 2, and still does after `cat shape/Makefile >> Makefile` | | S + `GNUmakefile`, S + `makefile`, S + `Makefile` + `GNUmakefile` | no command; the NOTE says to merge, "and then run `make bootstrap`" | `make bootstrap` exits 2 | ## What changed `adopt-project.py`, `_next_steps`: - It takes `surviving=`, the paths the plan's entries send to the root (`paths_for["root"]`, passed by `cmd_execute`). - It asks them for a kept `GNUmakefile` or `makefile` in GNU make's order, with `_root_keeps`, as `_makefile_follow_up` asks them. So NEXT names the same file as the plan's `CONTRACTS_DIR` follow-up. - Where it finds one, the command is `python3 scripts/bootstrap.py`. The NOTE names the file in the follow-up's words, and says `make bootstrap` and the shape's other make targets run there only once that file has them, or includes a Makefile that does: - the shape's `Makefile` is the root's: "NOTE the shape's Makefile is the root's `Makefile`, but GNU make at the root reads the source's `GNUmakefile`, the first of `GNUmakefile`, `makefile` and `Makefile` it finds, so `make bootstrap` and the shape's other make targets run there only once `GNUmakefile` has them, or includes a Makefile that does; `python3 scripts/bootstrap.py` is what the shape's `make bootstrap` runs." - the source also keeps its own `Makefile`: "NOTE the root keeps its own Makefile and the shape's is shape/Makefile, and GNU make at the root reads ..." (the same clause, and the same ending). - the source's own script went aside too: the merges first, as before, "and then run `python3 scripts/bootstrap.py`; GNU make at the root reads ...". - A root that keeps neither name gets the block it got before, byte for byte. That includes `MAKEFILE`, which GNU make never reads where case is kept. ## Tests `tests/test_adopt_next_make_reads_first.py`, new, 40 tests (38, and the 2 of the fix round below): - 13 rows word for word: none, `Makefile`, `makefile`, `GNUmakefile`, `makefile`+`GNUmakefile`, `Makefile`+`makefile`, `Makefile`+`GNUmakefile`, all three, S, S+`Makefile`, S+`GNUmakefile`, S+`makefile`, S+`Makefile`+`GNUmakefile`. - 3 rows that keep `make bootstrap`: `MAKEFILE`; longer names (`makefile.am`, `Makefile.in`, `GNUmakefile.bak`); and the names below the root. - 13 rows in which NEXT names the same file as `_makefile_follow_up`, for the same root paths. - 9 end to end rows, one for each row that keeps `GNUmakefile` or `makefile`. Each is a real `execute`, and its clone is made by pasting NEXT's own clone line. In that clone: - NEXT's lines are the row's; - `make which` prints the file the NOTE names; - `make -n bootstrap` has no rule; - the printed command exits 0 and ends `bootstrap ok`. For S rows, it does so once the shape's script is merged, as the NOTE says; - where a `Makefile` is kept, the follow-up's merge alone still leaves no rule; - an `include Makefile` in the file GNU make reads makes `make -n bootstrap` run the shape's recipe. On main's `adopt-project.py`, the first 38 all fail. The 9 end to end rows fail on what NEXT prints. The unit rows that guard the unchanged cases fail there only for want of the `surviving=` keyword. M1 below shows the same rows passing and every row that keeps `GNUmakefile` or `makefile` failing. On this head, all 40 pass. Mutants of the change, each restored and checked by sha256. Each run is the new file plus the five `next` tests of `test_adopt_e2e.py` (43 tests). Every mutant is killed: | mutant | failed | |---|---| | M1 `first` never found | 27 | | M2 `makefile` asked before `GNUmakefile` | 6 | | M3 the command ignores `first` | 6 | | M4 the branch for a `Makefile` kept beside `first` dropped | 6 | | M5 the branch for a script gone aside ignores `first` | 9 | | M6 the lookup folds case | 7, two of them existing `test_adopt_e2e.py` rows | | M7 `cmd_execute` passes no paths | 9, the end to end rows | Gate on this head, named files only, one basetemp per run. Each run had `EMAIL` set, `GIT_CONFIG_GLOBAL` an empty file, `GIT_CONFIG_NOSYSTEM=1`, a stub `gh` first on PATH, `GH_TOKEN=bogus` and an empty `GH_CONFIG_DIR`: - the new file, `test_adopt_make_reads_first`, `test_adopt_e2e`: 98 passed, with the new file at 38 tests (at `c04bb710`; the fix round's gate is below). - `test_adopt_github_exits`, `test_adopt_printed_sentences`, `test_repo_hygiene`, `test_readme_windows_quoting_sentence`, `test_cli_reference`, `test_agents_md_codes`: 291 passed. - `test_scaffold_leg_path_collisions`: 104 passed. - `test_adopt_contracts_dir`, `test_adopt_verify`, `test_adopt_pins`, `test_adopt_root_manifest`, `test_adopt_ignored_shape_files`, `test_adopt_nested_attributes`, `test_adopt_shape_pin_and_work_dir`: 378 passed. - `test_shape_doctor.py` in 4 shards: 112 + 112 + 112 + 109 = 445 passed, and 3 skipped (the PowerShell line, which is printed only on a Windows host). ## Fix round 1 (RV1121's SHOULD-1) RV1121 found the code right and one test missing: nothing held `cmd_execute` to handing `_next_steps(surviving=)` only the root's paths. Its mutant X4, which passes `paths_for["root"] + paths_for["code"] + paths_for["drop"]`, kept all 43 tests green. The case is a real one: the `root-project-makefile` rule sends a kept `GNUmakefile` to the root, and its reason says that moving a Makefile that only builds the code to the code leg "is a one-line edit of the plan" (`contracts/path-classification.yaml`, lines 236-237). Two end to end rows, `test_next_keeps_make_bootstrap_where_the_plan_sends_the_makefile_elsewhere[code]` and `[drop]`. Each keeps `GNUmakefile` in the source, edits the plan to send it to `leg: code` (then `leg: drop`), runs a real `execute`, and pastes NEXT's own clone line. Each asserts that NEXT's lines after the clone are ` make bootstrap` and nothing else, that the clone's root has no `GNUmakefile`, and that `make -n bootstrap` there exits 0 and runs `python3 scripts/bootstrap.py`. The `execute`, paste and clone steps of the nine existing end to end rows moved into one helper, `executed_and_cloned`, which the two new rows share, so no block of the test is written twice. Those rows assert what they asserted. The file now holds 40 tests (38 and 2). - **X4, on this head's `adopt-project.py` with the one line changed:** the new file and the five `next` rows of `test_adopt_e2e.py` (45 selected): 2 failed, 43 passed. Both new rows fail on NEXT's lines, which are `python3 scripts/bootstrap.py` and the NOTE "the shape's Makefile is the root's `Makefile`, but GNU make at the root reads the source's `GNUmakefile`, ...", naming a file the root does not have. The file was restored and its sha256 read `60bad59a...` again. - **On main's `adopt-project.py`:** the two rows pass, since main printed `make bootstrap` there. They hold the unchanged case against the new lookup, and X4 is the mutant that tells them apart. - **On this head `1c5173758`**, same environment as above: the new file 40 passed, `test_adopt_e2e.py` 27 passed, `test_repo_hygiene.py` 100 passed. - **Merge:** `git merge-tree --write-tree` of this head with main at `823624d93`: clean (tree `73ddafb2`). RV1121's four NITs are not in this PR; the lane files them. ## Merge - `git merge-tree --write-tree` of this head with main at `6da757cbe`, where the branch starts: clean (tree `21955cc7`). Main has since moved to `7cd66902a`, which changes `scaffold-project.py`, its test and the docs, and none of this branch's files. The merge with it is clean too (tree `ec4e6c8e`). - With #1105's head `5a4de7f76`: clean (tree `3394d19d`). #1105's `adopt-project.py` change is in `_predict_collisions`, apart from `_next_steps`. On that merge, built in a `git clone --shared` checkout, `test_adopt_next_make_reads_first`, #1105's `test_adopt_collision_make_reads` and `test_pinned_copy_text` gave 152 passed. - With the other open pull requests that change `adopt-project.py` (#1120 `c55e55e3`, #1119 `bdcf9952`, #1118 `c60066cb`, #1106 `504a1dd6`, #1091 `160de118`): each clean. ## Not in this PR - Once #1105 lands, the lookup "which of `GNUmakefile` and `makefile` the root keeps first" is written three times: in `_makefile_follow_up`, `_predict_collisions` and `_next_steps`. Each is the same two lines over `GNU_MAKE_READS` and `_root_keeps`. They are kept apart here so this PR and #1105 touch disjoint lines; one helper could serve all three after both land. - `templates/assembly-root/AGENTS-shape.md` line 19 ("`git clone --recurse-submodules` this repository and run `make bootstrap`") has the same gap in an adopted root that keeps `GNUmakefile` or `makefile`. It is pinned, and #1105 changes that file, so it is left. - On a disk that ignores case (#1049), a kept `makefile` is reported as a `Makefile` collision, and NEXT's NOTE says "the root keeps its own Makefile". The new clause is true there too, since GNU make reads that file and merging into it brings the targets. The spelling is #1049's. - A kept directory named `GNUmakefile/` or `makefile/` is named as the source's file. GNU make stops on it with "Is a directory". `_makefile_follow_up` does the same. Closes #1047 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…` 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) `check` promises never to write into the source, and a partial clone (`git clone --filter`) broke that through git itself. A read that needs an object the clone lacks fetches it from the promisor remote. That runs the `upload-pack` the source's config names, and writes a promisor pack into the source's `objects/`. - `check` and `execute` now run git with `GIT_NO_LAZY_FETCH=1`. They refuse a partial-clone source that lacks an object its branches, tags or HEAD reach as `source-partial-clone`. The refusal names how many objects it lacks and the cure for each promisor remote, `git fetch --refetch --no-filter <remote>`. The walk is `git rev-list --objects --missing=print --branches --tags HEAD --`. - Measured with git 2.43, on a `--filter=blob:none --no-checkout` clone: - On base, `check` ran `upload-pack` 26 times and wrote 26 promisor packs into the source. `execute` failed with `Blob not found` after both leg repositories were made. - On head, both exit 2 with `source-partial-clone`, run nothing, leave `objects/` unchanged and make no leg. - A full clone and a cured partial clone adopt as before. - `tests/test_adopt_partial_clone_source.py` holds the rows, and `docs/cli.md` gains the help text. Review: lane 1's independent reviewers found `2c8082a2` NOT READY for one SHOULD (#1180 issuecomment-6351633105), and `6cd96b8b` NOT READY for one SHOULD (issuecomment-6356200375). They found `b316f415` provisionally READY (issuecomment-6364551003), and `a4ec51d2` READY with 4 NITs (issuecomment-6372009946). After #1119 landed, the branch was restacked onto main by three merges, and its changed lines equal the reviewed delta. Lane 1's delta check of the NIT round `a0e8cded` found the tool's AST unchanged apart from docstrings, and the two new rows kill the surviving mutants M15 and M17 (issuecomment-6374457465). Tests, under a stub `gh` with a bogus token: at `a0e8cded`, lane 1's gate gave 650 passed and 3 skipped over the PR's test file, the throwaway and single-repository-record files, the doc trio, the path-guard and no-auto-maintenance scans, `tests/test_scaffold_leg_path_collisions.py` and `tests/test_doctor_partial_clone.py`. The doctor's 4 shards passed on #1190's head `359a749c`, whose tree is this PR's plus #1190's change. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its H0 `a0e8cded` is green on Linux and SonarCloud. The branch was never rebased. After H0, the only commit is update-branch's clean merge of main, giving 58a2175, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: 2 pytest calls over `tests/test_adopt_partial_clone_source.py`, `tests/test_path_guard_hygiene.py`, `tests/test_no_auto_maintenance.py`, `tests/test_scaffold_leg_path_collisions.py`: 321 passed, 2 skipped. Closes #1145 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… parent's `-c` settings, `attr.tree` too, and the source's objects, `GIT_ATTR_SOURCE` is refused by name before any question, and each throwaway reads its environment first (#1136) (#1169) `check`'s throwaways from #910 (the rehearsal, `_stored_as_written`, `_AttributesProbe`) were made with `_inherited_env()`. That leaves out a parent git's `-c` settings (`GIT_CONFIG_PARAMETERS`), `GIT_ATTR_SOURCE` and any `attr.tree` in `GIT_CONFIG_COUNT`. `_stored_as_written` held none of the source's objects, and the rehearsal chose the files it asks about with `check-attr --cached`, which reads no `attr.tree`. So `check` answered as a git with none of them would. `execute` then stopped after both legs existed, or refused a plan its own git would carry out. - The rehearsal and `_stored_as_written` now take a parent's `-c` settings, `attr.tree` included, and the source's objects. Each throwaway reads its environment before it is made. - `GIT_ATTR_SOURCE` is refused by name before any question is asked. - The rehearsal's attribute reads run without `--cached` (ruling X1), so a parent's `attr.tree` reaches them as it reaches `execute`'s git. - Measured with git 2.43, against base `bba168bd`: - A parent's `-c core.protectHFS=true`, or a `required` clean filter that fails, used to pass `check` and stop `execute` with both legs made. Now `check` refuses it, and `execute` refuses it before any leg. - A parent's `attr.tree` naming the empty tree used to refuse a plan `execute`'s own git carries out. That plan is now `plan ok` and adopts. - Known limits: under a failing filter that two places' rules name, a misnamed second finding comes beside the right first one. `update-index -q --refresh`'s exit is not read, and the trace is on #1192. Under `attr.tree`, the copies finding asks for lines in the root `.gitattributes`, which git does not read there. Review: lane 1's independent reviewers reviewed each round. r1169c's review of `76c9b767` stopped part-way with no MUST, and r1169d finished it NOT READY for two SHOULDs (#1169 issuecomment-6374351753). The fix round `d9224ecf` adds r1169d's own row (l), which kills mutant K3 (3 of 3), and corrects the body. r1169e found `d9224ecf` READY, with no MUST or SHOULD (issuecomment-6375188826). After #1119 landed, the branch was restacked onto main by three merges, and its changed lines equal the reviewed delta (sha256 `5c59dd53`). Tests, under a stub `gh` with a bogus token: at `d9224ecf`, lane 1's gate gave 1,253 passed and 9 skipped over 7 calls. They covered the PR's five test files, the single-repository-record file, the no-auto-maintenance and path-guard scans, `tests/test_scaffold_leg_path_collisions.py`, `tests/test_adopt_root_symlinks.py`, and `tests/test_shape_doctor.py` in 4 shards. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its H0 `d9224ecf` is green on Linux and SonarCloud. The branch was never rebased. After H0, the only commit is update-branch's clean merge of main, giving 989295b, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: 7 pytest calls over `tests/test_adopt_check_throwaway_repos.py`, `tests/test_adopt_single_repository_record.py`, `tests/test_adopt_nested_attributes.py`, `tests/test_adopt_reencoded_files.py`, `tests/test_command_scopes_built_at_the_test.py`, `tests/test_no_auto_maintenance.py`, `tests/test_path_guard_hygiene.py`, `tests/test_scaffold_leg_path_collisions.py`, `tests/test_adopt_root_symlinks.py` and `tests/test_shape_doctor.py` in 4 shards: 1,253 passed, 9 skipped. Closes #1136 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s message spells a path byte that is not UTF-8 as `_shown` does, an escape, and no longer says it names it with a `?` in its place, which #900 replaced (#1018) (#1104) `env_commit`'s docstring in `scripts/shape_materialize.py` says what the split commit's message does with a `root` or `drop` path that holds a byte that is not UTF-8, as the code does since #900. It said the message names such a path "with a `?` in place of that byte, a path that does not exist". That was true before #900, and it is not true now. A reader of the docstring is told about a `caf?.md` the tool no longer writes. Found by lane 1's writer on #900 and filed as #1018. Lane 5 took it outside v1.2.0, under Brett Heap's ruling of 2026-10-21T10:32:04Z. Measured on main `2a119f281` and again on `16f9a0e81` (main after #1087), with a small script that calls: `adopt-project.py`'s own `_split_message` and `_shown`, then `env_commit` and a real `git commit`, read back with `cat -A`. - The issue's claim held: the docstring (about line 2103 when filed, line 2268 on `2a119f281`, line 2349 on this branch) still said the message names the byte with a `?`. - The code does not. `_shown("caf\udce9.md")` is `` `caf\udce9.md` ``, and the split message for a `root` and a `drop` entry named that holds no lone surrogate. Through `env_commit` and `git cat-file commit HEAD`, `cat -A`: ``` DROPPED (in no leg and no longer here):$ `caf\udce9.md`$ STAYS IN THE ASSEMBLY ROOT:$ README.md$ `caf\udce9.md`$ ``` - What stays true of `env_commit` itself: a message that holds the lone surrogate, handed to it directly, is still written as `?`. `cat -A` reads `Name: caf?.md$`. So the `errors="replace"` paragraph is kept, and what changes is who it describes. - #1087 (main `16f9a0e81`) changed `scripts/shape_materialize.py` by 128 lines, none of them in `env_commit`, `COMMIT_COMMAND` or the message, and changed nothing in `adopt-project.py` (0 diff lines). The rebase onto it was clean and the docstring needed no other change. One file, `env_commit`'s docstring, in two paragraphs: the `errors="replace"` paragraph (round 1, 11 lines added and 7 removed) and the sentence above it (round 2, below; 3 added and 2 removed). Together 14 added and 9 removed against main `6da757cb`. The `errors="replace"` paragraph now says: - encoding, a character UTF-8 cannot carry is written as `?`, and nothing raises after both legs are pushed, as before; - `adopt-project.py` refuses a lone surrogate in a path it extracts (`entry_path_problem`), and a `root` or `drop` entry that holds one is listed in the split message but never raw: the message names each path as `plan`'s report does (`_shown`, #900), so the byte 0xE9 reads as an escape with no surrogate in it, `` `caf\\udce9.md` ``, the path the source holds; - written raw it would be a `?`, `caf?.md`, a path that does not exist, which is what `env_commit` still writes for a lone surrogate it is handed. The backslash is doubled in the source, as the neighbouring docstrings write `\\udcc3`. A single `\u` in a non-raw docstring would be an escape that makes a lone surrogate. Measured: the docstring holds no character from U+D800 to U+DFFF and holds the six characters `\udce9`. The longest docstring line is 78 characters. **The code is unchanged.** A script compares the AST of `scripts/shape_materialize.py` with every docstring blanked: the branch's file is identical to `origin/main`'s after the rebase onto `16f9a0e81` (round 1), and the merge of this head into `origin/main` `6da757cb` is identical to `6da757cb`'s file (round 2). **No pin moves.** `scripts/shape_materialize.py` is not a pinned copy. `COPIED_VERBATIM`, `COPIED_FROM_SHAPE`, `FAMILY_COPIED_VERBATIM` and `FAMILY_COPIED_FROM_SHAPE` do not list it, and `templates/assembly-root/scripts/validate-pins.py` says of it "which no project carries". So no `upstream-changed` row, no digest, no assembly copy and no `tests/test_pinned_copy_text.py` entry changes. **No other text pins these words.** Searched in `tests/`, `README.md`, `docs/`, `docs/handbook.html`, `AGENTS.md` and `templates/`: no test reads `env_commit`'s docstring (`__doc__`, `getdoc`), and none of them holds "in place of that byte" or "a path that does not exist" for this. The other places that name the `?` are about this function's behaviour or about history, and are still true: `_split_message`'s docstring says `caf\\udce9.md` "was `caf?.md`" (before #900), `_refused_by_policy` already says the message "lists it as `_shown` does (#900)", and `tests/test_adopt_spelled_names.py` quotes the message as it was on `c7a3ff16`. The `?` in `argv_text`, `trailer_line` and the commit-trailer text is about a `--trailer`, not the split message. No rows and no mutants: the change is a docstring, the code is the same AST, and no test pins the docstring. So there is no row to fail on main and nothing to mutate. All with `$Q/v110/run9.sh`, `EMAIL=service@opensoft.dev`, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`, the stub `gh`, a fake token, git 2.43.0 (`/usr/bin/git`). Two shards, run before the rebase, at the `2a119f281` base with this one-file change in the tree. Together they are every `tests/*.py` that names `shape_materialize` or `env_commit` (86 files, including `tests/test_adopt_spelled_names.py` and `tests/test_adopt_entry_paths.py`, which run `env_commit` and the #900 message) and `tests/test_agents_md_codes.py`: | run | files | result | |---|---|---| | shard A | 43 | 2873 passed, 4 skipped in 1707.51s, rc=0 | | shard B | 44 | 4611 passed, 31 skipped in 2462.01s, rc=0 | After the rebase onto `16f9a0e81`, on the committed head `e32b2703d733f6c60c1b2cba2d62d164d93d7b9a` (`dirty=0`): the trio, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py`, **170 passed in 55.67s**, rc=0. The docstring-only change needed no new shards: the shards' counts are runs at the `2a119f281` base, and #1087 did not touch `env_commit`. Round 2, on the committed head `de89aa81b1533e32f1007f991aeae05cd915b36d` (`dirty=0`): the same trio, **170 passed in 61.77s**, rc=0. The round 2 commit is one docstring sentence, so it needed no new shards either. git 2.47.1: no run. The change adds no test file of its own, so there is none to run on it. `git merge-tree --write-tree HEAD <other>` with HEAD at `de89aa81b1533e32f1007f991aeae05cd915b36d`, after a re-fetch of `origin main` with `-c gc.auto=0 -c maintenance.auto=false` (round 2, about 14:03Z on 2026-10-21): | against | result | |---|---| | `origin/main` `6da757cbe9a4eb66d60c34f38dcc2b332d175e8b` | clean, rc 0, tree `15c11b6c959fc4c7540105d5d62e3f961c8e27fd` | | #1105 `fix/1048-makefile-follow-up-gnu-read-order` `5a4de7f763b5a3c32adce9614a3a539508416eb4`, an open PR that touches `scripts/shape_materialize.py` | clean, rc 0, tree `db05aead67052643942e35a9689d9dd4a0873fda` | | #1118 `fix/1089-named-link-skip-tests` `c60066cbeec18b8b6c926e254ed4bee6b9a486c2`, the other open PR that touches `scripts/shape_materialize.py` | clean, rc 0, tree `a40df3b63c32c2a3d8f4c40587b094940c8d6567` | Read with `gh pr list --state open --json number,headRefName,headRefOid,files`: 16 open PRs, three of them (this one, #1105 and #1118) touch `scripts/shape_materialize.py`, the only file this change touches. The other 13 (#1120, #1119, #1117, #1111, #1109, #1107, #1106, #1102, #1101, #1098, #1097, #1091, #1088) touch no file this change touches. The same command with HEAD at the reviewed head `e32b2703` gives rv1104's trees: `de31d857` against `6da757cb` and `195ffe19` against #1105. The round 1 merge against #1090 is moot: #1090 has landed in `6da757cb`. Held as a draft until the v1.2.0 tag; it is now on the v1.2.1 milestone. - **NIT 1, `scripts/shape_materialize.py` lines 2340-2343, a line round 1 did not change.** The paragraph above the reworded one said the adoption's split message "names each entry path as the source spells it". Since #900 that is true of a printable path alone: `_split_message` lists any other through `_shown`, as the escape the next paragraph describes. It now says "as the source spells it (a printable one; see below)". One new commit on top of `e32b2703`, `de89aa81b1533e32f1007f991aeae05cd915b36d`, 3 lines added and 2 removed, docstring only, pushed as a fast-forward (no rebase, the PR is open). - Measured: the AST with docstrings blanked is identical to `e32b2703`'s and, merged into `6da757cb`, to `6da757cb`'s. The docstring holds no lone surrogate, still holds the six characters `\udce9`, and its longest line is 78 characters. - Searched `tests/`, `README.md`, `docs/`, the handbook mirror, `AGENTS.md`, `templates/` and the scripts for "names each entry path", "as the source spells" and the new words: only `tests/test_adopt_entry_paths.py` line 233 matches, a test's docstring about `execute` handing each path on as the source spells it, which is another sentence and pins nothing here. - **NIT 2, this body.** It named main as `16f9a0e81` and #1090 as the one open PR that touches the file. #1090 has landed. Main is `6da757cb`, and the merge-trees above are redone against it, against #1105 and against #1118, a draft that opened since and also touches the file. Review: rv1104-1107, lane 5's independent opus reviewer, which wrote none of #1104: READY at `de89aa81`, H0 of this landing (issuecomment-6318111413), with no finding; NIT 1 and NIT 2 of the round before are fixed. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 de89aa8 passed tests, tests-git-bash and SonarCloud; the head 92285d4 is a clean update-branch merge of main b66d8f0, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 6bd2128 over main b66d8f0: the repo_hygiene, agents_md_codes, cli_reference sets, 171 passed. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green". Closes #1018 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eeps a `GNUmakefile` or a `makefile`, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file says `make` reads the first of the three and the block reaches it only once that file holds it or includes a Makefile that does (#1048) (#1105) Two sentences said a kept `Makefile` is what holds the shape's block back. GNU make reads the first of `GNUmakefile`, `makefile` and `Makefile` it finds, and only that one, so where the root also keeps `GNUmakefile` or `makefile`, `make` reads that file, and reaches the merged `Makefile` only where that file includes it. **Measured** (a real `execute`, then `make -n` in the clone, for each kept name; r1046's NIT-B, repeated; the include rows are RV1105's SHOULD-1): | the source keeps | file `make` reads | after `cat shape/Makefile >> Makefile`, `make -n bootstrap` | |---|---|---| | `Makefile` | `Makefile` | exit 0 | | `Makefile` + `makefile` | `makefile` | exit 2, "No rule to make target 'bootstrap'" | | `Makefile` + `GNUmakefile` | `GNUmakefile` | exit 2, same | | all three | `GNUmakefile` | exit 2, same | | `Makefile` + a `makefile` or `GNUmakefile` that ends in `include Makefile` | that file | exit 0, runs `python3 scripts/bootstrap.py` (exit 2 before the merge) | | `makefile` or `GNUmakefile` alone | that file | no merge to make: the shape's `Makefile` is the root's, and `make contracts-dir` has no rule until that file holds the block or includes `Makefile` | **What changed** - `scripts/shape_materialize.py`, `collision_follow_up`: takes `reads_first`, the file GNU make reads before the shape's `Makefile`. Where it is set, the Makefile's follow-up says GNU make reads that file, the first of the three it finds, and that the shape's make targets reach `make` there only once that file has them, or includes a Makefile that does; until then `python3 scripts/bootstrap.py` is what the shape's `make bootstrap` runs. Where `scripts/bootstrap.py` went aside too, it says neither command runs the shape's bootstrap until the script is merged, and then `python3 scripts/bootstrap.py` runs it. Where the root keeps only the source's `Makefile`, the sentence is what it was, byte for byte. `GNU_MAKE_READS` joins `SHAPE_MAKE_TARGETS`. - `adopt-project.py`, `_predict_collisions`: reads the file from the plan's root paths as `_makefile_follow_up` does, so the `CONTRACTS_DIR` line and the collision line of one plan name the same file. - `templates/assembly-root/AGENTS-shape.md`: says `make contracts-dir` prints the block wherever `make` reads the shape's Makefile, names the three files in the order the code reads them, and says that where a `GNUmakefile` or `makefile` is kept, beside a `Makefile` or instead of one, `make` reads it, and not the shape's Makefile unless that file includes it. It is one verbatim copy: the digest in every project's `contracts/shape-pin.yaml` is rendered from its bytes at adopt and scaffold time, and no digest or `tree_sha256` of it is stored in this repository, so there is no pin to move here; each project re-pins through `update-shape.py` as for any template change. - `tests/test_pinned_copy_text.py`: `ADOPTED_SENTENCES` holds the new text. - `tests/test_adopt_collision_make_reads.py` (new, 31 rows): the follow-up per kept set, the two lines of one plan naming one file, no collision line where no `Makefile` is kept, the two name tuples equal, the pinned file naming every file the code reads first, the script-aside tail where `make` reads another file, and, in a real adoption's clone, `make -n` held to both sentences for each kept name and for a kept `GNUmakefile` or `makefile` that ends in `include Makefile`. **Fix round 1** (RV1105's review, `b28f40de0` on top of `5a4de7f76`) - **SHOULD-1:** the follow-up said the targets reach `make` "only once `X` holds them, or includes a Makefile that does, and not with that merge alone". The last clause is untrue where `X` already ends in `include Makefile`: the merge alone brings them. The clause is gone, and the docstring paragraph, the `SHAPE_MAKE_TARGETS` comment and the `ADOPTED_SENTENCES` comment now say the merge alone brings the targets only where that file includes `Makefile`. - **NIT-1 (and RV1121's NIT-3):** the follow-up now uses the words of #1121's NOTE: "has them", and "is what the shape's `make bootstrap` runs". `_next_steps` is not touched. - **NIT-2:** the script-aside tail, where `reads_first` is set, said "neither `make bootstrap` nor `python3 scripts/bootstrap.py` runs the shape's bootstrap before both merges are made", which reads as if `make bootstrap` runs it after them. It now says neither runs it until the script is merged, and then `python3 scripts/bootstrap.py` runs it. I did not keep "before both merges": where `X` is given the targets itself, as the sentence before invites, `make bootstrap` runs the shape's bootstrap once the script is merged, with no `Makefile` merge at all. - **NIT-3:** the template's "and never the shape's Makefile" is now ", and not the shape's Makefile unless that file includes it". `ADOPTED_SENTENCES` changes with it. No other copy or regex in `tests/` pins that sentence (`git grep` for "never the shape" and "reads the first of them"). - **NIT-4:** the `collision_follow_up` docstring no longer says `execute` reports through it. It says `plan` predicts the follow-ups and stores them in `follow_ups:`, the split commit carries those, `execute` names its collisions by path only (its `beside` lines and the "WRITTEN BESIDE" list), and `Materialized.follow_ups()` has no caller. The method stays. - NITs 5 to 8 are left to the lane. **Rows of this round:** `tests/test_adopt_collision_make_reads.py` and `tests/test_pinned_copy_text.py`, 118 rows. With this round's test files on `5a4de7f76`'s code, 17 fail and 101 pass; on `b28f40de0` all 118 pass. The 17 fail for the right reason: | rows | why they fail on `5a4de7f76` | |---|---| | 4 collision rows (`Makefile+makefile`, `Makefile+GNUmakefile`, all three, `Makefile+GNUmakefile+scripts`) | the line holds "not with that merge alone" | | 2 new script-tail rows (`GNUmakefile`, `makefile`) | the line holds "before both merges are made" | | 2 new include rows in a real clone (`GNUmakefile`, `makefile`) | `make -n bootstrap` exits 2 before the merge and 0 after it alone (both asserted first, and passing), then the split message holds "not with that merge alone" | | 7 clone rows, the pinned-file row and `test_a_scaffolded_root_is_told_its_makefile_exports_none` | the pinned file still says "and never the shape's Makefile" | **Mutants of this round**, each run against the 118 rows on `b28f40de0`, each file restored and its sha256 checked: | id | mutant | rows failing | result | |---|---|---|---| | MA | ", and not with that merge alone" put back | 6 | killed | | MB | the script-aside tail put back ("before both merges are made") | 3 | killed | | MC | the template's "and never the shape's Makefile" put back | 9 | killed | | MD1 | include rows: `not in message` inverted to `in message` | 2 | killed | | MD2 | include rows: after the merge, `returncode == 0` inverted | 2 | killed | | ME | "holds them" put back | 6 | killed | | MF | "the shape's `bootstrap` target runs" put back | 5 | killed | **Gate** on `b28f40de0`: | files | passed | failed | skipped | |---|---|---|---| | `test_adopt_collision_make_reads`, `test_pinned_copy_text`, `test_adopt_make_reads_first`, `test_adopt_contracts_dir`, `test_adopt_printed_sentences`, `test_adopt_e2e`, `test_repo_hygiene`, `test_scaffold_leg_path_collisions` | 470 | 0 | 0 | | `test_shape_doctor`, 8 shards (62 + 56 + 54 + 56 + 60 + 57 + 58 + 45 collected) | 445 | 0 | 3 (PowerShell rows, Windows host only) | | **total** | **915** | **0** | **3** | **Merge-tree:** `b28f40de0` against main `4ed7c7bf5` is clean, and so is it against each open PR that touches these files: #1104, #1106, #1118, #1119, #1120, #1121 (`1c5173758`), #1122, #1123 and #1125. **Round 0** (`5a4de7f76`, main `6da757cbe` merged in): on main's code (plus the constant alone), 12 of the then 27 rows failed and 15 passed; the 30 test files that run the changed code or read the template gave 2109 passed, 0 failed and 4 skipped (3 PowerShell rows in `test_shape_doctor.py`, 1 MedxEHR row in `test_adopt_plan.py` that needs a clone `SHAPE_MEDXEHR_CLONE` names); nine mutants each failed 1 to 11 of the then 114 rows. **Not here** - The third box of #1048, the `_next_steps` NOTE in `adopt-project.py`, is covered by the change #1047 makes to it: see #1047 and #1121. - `templates/assembly-root/AGENTS-shape.md` line 19 and the five-commands table say `make bootstrap` without this caveat. They say nothing about a kept file, so they are outside #1048's two sentences. Closes #1048 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



What
adopt-project.py checkasks this machine's git what the split will make ofthe source in throwaway repositories in the work directory (#910). Two
questions were still asked otherwise, and on git 2.43 both stopped short of
the answer
executemeets.checknow makes the split's owngit cloneof the source first, in a throwaway directory in the work directory:git -c protocol.file.allow=always -c core.hooksPath=<empty> clone -q --no-checkout --shared <source> <throwaway>/repository(_split_clone_asked). The protocol setting isFILE_PROTOCOL, the oneexecutegives each of its clones, and the operator's template is copied in, as_extract_leg's clone copies it. Its environment is_split_clone_env():_inherited_env(), plus the-csettings a parent git hands this process (GIT_CONFIG_PARAMETERS), asexecute's clone takes them. Left out are the checkout (the rehearsal and_paths_git_refusesask it), this machine's hooks, and every variable that names another repository, its objects, an index or a work tree, which the clone would write into. Where git will not make the clone, that is oneadopt-git-unaskedfinding in git's words, the questions after it are not asked, andexecuterefuses it before it makes either leg. Where git makes it, nothing changes._resolvedand_answers_throwawayand_throwaway_made. That means the work directory, no hook (-c core.hooksPath=<empty>on every command), no fsmonitor program or daemon (-c core.fsmonitor=false, as the rehearsal's and #619's commands have it; r1119b's SHOULD-A),--template=<empty>,-b throwawayand the source's object format, all under_inherited_env(), read before the throwaway is made. Before, it wasTMPDIRand a plaingit initwith the caller's whole environment. A git step that fails in one isadopt-git-unaskedin git's words, through the new_throwaway_git, never "a git or gh command failed"._submodule_findingsasks no url question once a throwaway in the work directory could not be made or asked. Where a url question itself cannot be asked, the url findings asked before it are kept, and that_Unaskedbecomes the one finding for the throwaways._resolvedtakessourceas a positional fourth parameter and_answerstakessource=None, so the existing rows that patch or call them keep working.tests/test_adopt_reencoded_files.py::test_a_link_the_checkout_writes_is_never_written_throughsaid the throwaway is inTMPDIR. It now says the work directory; both are as deep belowtmp_path, so the row still asks the same thing. (The issue'sadopt-project.py:466names_pin_row_problemat 74c1770; this docstring is the one PR #1031's body named.)tests/test_adopt_root_symlinks.py::test_check_says_where_git_would_not_make_the_ref_probe(#1091) setGIT_DEFAULT_HASH=bogusto fail the ref probe'sgit init. Merged with this branch, that variable stops the split's own clone first, sochecksaidadopt-git-unaskedand exited 1 where the row wants 0 (r1119b's MUST-2). The row now puts a stubgitfirst on PATH that fails only agit initof arefs-probe-*directory, in git's words (fatal: unknown hash algorithm 'bogus', exit 128), and hands every other command to the real git. It is skipped on Windows, as the other stub rows are. It passes on main's tool and on this head.Covers #1032, which lane 4 released into this issue: item (b) is that work.
The clone's cost. With
--sharedthe clone copies no object of a sourcethat is not shallow: it borrows the source's. git ignores
--sharedfor ashallow source, and says nothing, so that clone is a full copy of the
source's objects. r1119 measured a shallow copy of a 100k-file source with a
115 MiB pack: 7.3 s and 10.2 s, and 117 MiB. That is a cost, not a fault:
each of
execute's own clones of such a source is a full copy too. For asource that is not shallow, r1119 measured 0.06 s and 0.1 MiB (openRepoShape)
and 0.8-1.6 s and 1.2 MiB (100k files, 20,001 refs).
Such a clone also runs the
uploadpack.packObjectsHookthat this machine'sglobal or system config names, to pack what it copies, as each of
execute'sclones of that source runs it (r1119b's NIT-C). Measured on git 2.43 with a
shallow source and a counting hook:
check, and 1 in a clone made as_extract_legmakes it;git -c: 0 and 0.Measured
check --work-dirandexecute --local-remote-diragainst a fresh source, ongit 2.43.0, with a stub
gh. Before is main2a119f28(itsadopt-project.pyis unchanged through
16f9a0e8), after is this branch.check/executecheck/executeinit.defaultBranch = a..bin the global configplan ok/ exit 2REFUSED extracting the spec leg,fatal: invalid branch name: init.defaultBranch = a..b, afterNorthwind-spec.gitandNorthwind-code.gitexist; nothing rolled backFINDING adopt-git-unasked, "the split'sgit cloneof the source exited 128 there: fatal: invalid branch name ..." / exit 2REFUSED adopt-git-unasked;remotes/absentGIT_CONFIG_PARAMETERS="'init.defaultbranch'='a..b'"bdcf9952, this branch's first head)GIT_CONFIG_COUNTGIT_DEFAULT_HASH=bogusplan ok/ exit 2 "a git or gh command failed",fatal: unknown hash algorithm 'bogus', withNorthwind-spec.gitleft behindFINDING adopt-git-unasked("unknown hash algorithm 'bogus'") / exit 2REFUSED adopt-git-unasked;remotes/absentinit.defaultBranch = a..bgit init -q <TMPDIR>/openreposhape-url-*/ the sameFINDING adopt-git-unasked/ exit 2REFUSED adopt-git-unasked;remotes/absentGIT_DEFAULT_HASH=bogusgit init/ the sameFINDING adopt-git-unasked/ exit 2REFUSED adopt-git-unasked;remotes/absentcore.hooksPathin the global configpost-index-changeruns (_resolved'supdate-index)check's output is byte for byte the samecore.fsmonitorprogram in the global config or inGIT_CONFIG_COUNT_resolved's two repositories: inTMPDIRover main038dafba's tool, and in the work directory at9b998936GIT_OBJECT_DIRECTORYnaming the source's own objectsplan ok; the source'sobjects/info/stays emptyobjects/info/alternatesinto the source.On git 2.55, which CI runs,
git clonetakesinit.defaultBranch = a..b(CIrun 39404718064 on #1031). There the clone question is no finding and every
other question is asked. The rows below follow this machine's git, or a stub,
and lean on no version.
Also measured, on git 2.43, for the clone's flags and environment:
protocol.file.allow = neverin the config, the clone exits 128 (transport 'file' not allowed) withoutFILE_PROTOCOLand 0 with it, asexecute's does;--sharedclone, clone with--no-checkout --shared(exit 0);-c core.hooksPath=<counting hooks>runs no hook in the clone, because git reads the clone's own-c core.hooksPathafter the parent's and takes a key's last value. Without the clone's own setting, the parent's ranreference-transaction4 times;attr.tree=bogusamong a parent's settings, which is why_inherited_env()leaves them out of the questions a checkout answers, still lets the clone through (exit 0);GIT_ALTERNATE_OBJECT_DIRECTORIESandGIT_SHALLOW_FILE, which_inherited_env()keeps, write nothing outside the clone. A non-empty file inGIT_SHALLOW_FILEstops the clone, as it stopsexecute's.Tests
tests/test_adopt_check_throwaway_repos.py: 70 rows at195d9936, and 90 on the merge heade0a98f13, which holds main's 20 rows of adopt-project.py:refused_paths'update-index -z --index-infois untested withcore.ignorecaseand case twins (aandA) on a case-insensitive disk #1033 as well (see "The merge of main" below). 2a119f2 had 29, and one of them is split in two (below)._throwaway_git's failure among them;GIT_CONFIG_COUNTandGIT_CONFIG_PARAMETERS. There are real-git rows underinit.defaultBranch = a..bforcheckandexecute, and stub rows for both. The stubgitrefuses agit clonewhose config sets a test key, so they hold on every version of git;_split_clone_env()'s own row: what it takes and what it leaves out;git-config-parametersfor the two hook rows;GIT_OBJECT_DIRECTORYnaming the source's objects (SHOULD-1);GIT_DIRnaming the source, whose own config rewrites the on-disk url.checksays what it says withoutGIT_DIR, and the source's config is unchanged;_resolved,_answersand the clone (NIT-3).core.fsmonitorprogram runs in none ofcheck's throwaways, from the global config orGIT_CONFIG_COUNT, with a control for each that runs it for agit add(SHOULD-A, 4 rows);--work-dirinside the source, whose config rewrites the on-disk url to a directory that holds nothing or to a network.checksays what it says with the work directory outside, prints no NOTE of a url it did not ask, and leaves the source's config as it was (NIT-A, 2 rows);GIT_CONFIG_PARAMETERS, which_answersleaves out, as its docstring says (NIT-B). Its control shows git expands the url so under it._split_clone_env()'s row appends its twoGIT_CONFIG_COUNTentries to those the environment holds, and asserts that the last entry kept is its own and that noattr.treeis kept. Built from{}, it failedtests/test_no_auto_maintenance.py::test_no_test_sets_a_git_config_count_of_its_own(r1119b's MUST-1).url_planand theGIT_DIRrow now build their source and plan through two shared helpers, and ask what they asked.bba168bd(45 of 1,197 new lines; the gate is 3%), from two pairs of repeated test bodies in this file, 10 and 14 lines each.195d9936is test-only and factors each pair into one module-level helper in the same file, which takes the function that builds the environment:check_asks_the_clone_a_default_branch, for thecheckrows underinit.defaultBranch = a..b:test_a_default_branch_is_asked_of_the_splits_own_clone(global config) andtest_a_parent_gits_default_branch_is_asked_of_the_splits_own_clone(its two parent-git forms);execute_refuses_a_default_branch_its_clone_will_not_take, for theexecuterows:test_execute_refuses_a_default_branch_its_clone_will_not_takeandtest_execute_refuses_a_parent_gits_default_branch_before_any_leg(its two parent-git forms).bba168bd(46 with blank lines left out), and in none at195d9936.test_a_default_branch_git_will_not_take_stops_no_questionis split in two:test_a_default_branch_is_asked_of_the_splits_own_clonefollows this git's own clone;test_a_default_branch_git_will_not_take_stops_no_throwaway_initasks the rehearsal alone, so the-bof every throwawaygit initstays pinned on git 2.43.tests/test_adopt_root_symlinks.py: main's ref-probe row, item (d).195d9936: 70 passed, and the ref-probe row passes.195d9936, the duplication commit:test_adopt_check_throwaway_repos.pygave 70 passed, 0 skipped, the same 70 ids with the same outcomes asbba168bd(70 passed, 0 skipped), each run in a clean clone.test_path_guard_hygiene.pyandtest_repo_hygiene.pytogether: 149 passed.d0bc40ce's tool, this round's base (9b998936merged with main038dafba): 2 failed, 69 passed, of the 70 rows and the ref-probe row. The 2 are the fsmonitor rows, 8 runs each.038dafba's tool: 33 failed, 38 passed.16f9a0e8's tool in the last round, row for row. Each fails for its own reason:plan okwhere the finding is due,DID NOT RAISE Refusalfromexecute, url repositories made inTMPDIR, two hook runs, an uncaught_Unasked, or a function or parameter that main does not have.TMPDIR), and the parent-rewrite row (main's_answersran in the whole environment, read the rewrite, and found the url answering nothing).git inituse a stubgit, first on PATH, and are skipped on Windows. One stub refuses everygit clone; another refuses one whose config sets a test key; the ref-probe row's fails only the probe'sgit init.executerows underinit.defaultBranch = a..bare skipped where this git's clone takes the name, as 2.55's does.core.ignorecase. No case-insensitive disk is at hand. The row is still sound:_paths_git_refuseshands paths toupdate-index --index-infoand readsls-files, which read no file of the disk, only the setting. So it setscore.ignorecase=trueon a disk that holds both twins.git addof the twins under it holds at most one (git 2.43 sayswill not add file alias).--index-infoholds both, so neither twin is named.Mutants
First round, at
bdcf9952: 16 mutants of the behaviour changes, each run over the new file (and M15 over the url files too). 13 were killed and 3 survived:git submodule initwithout the hook setting, survives: 73 rows of the url files pass. It is equivalent, asgit submodule initruns no hook.r1119's round, at
9b998936: 11 mutants, each run over the file with-x, and the tool restored and sha-checked after each. 11 killed.GIT_OBJECT_DIRECTORYrow_split_clone_env()is the whole environment_split_clone_env()'s rowGIT_OBJECT_DIRECTORYalone_split_clone_env()'s rowGIT_CONFIG_PARAMETERS, as atbdcf9952git-config-parametersrow (the stub rows too, measured overbdcf9952)-c core.hooksPathgit-config-parametersamong them_answersin the whole environmentGIT_DIRrow: the source's url rewrite gaveplan-submodule-url-answers-nothing_resolvedin the whole environmentGIT_DIRrow. Measured, it wrote[remote "origin"]and[submodule "u0"]into the source's own config, andchecksaidplan ok_resolvedreads its environment after making its throwawayresolved_answersdoes the sameanswerstrysplit-cloner1119b's round, on this head's tool before its last docstring edit (
cea25530,0b326952): 18 mutants, each run with-xover the rows named, and the tool restored and sha-checked after each. 17 killed; S4 survives as equivalent. A5, A6, A6b, A15, A2b, R8 and A18 are r1119b's own, re-anchored on this head._split_clone_env()hands backGIT_ATTR_SOURCE,GIT_INDEX_FILEorGIT_WORK_TREE_split_clone_env()'s rowGIT_CONFIG_PARAMETERSalways set, empty where the parent sets none_split_clone_env()'s rowGIT_CONFIG_COUNTform_split_clone_env()'s row_inherited_env()keepsattr.tree_split_clone_env()'s rowattr.treeand all_split_clone_env()'s rowCommandFailedstopscheckcore.fsmonitor=false, as at9b998936core.fsmonitor=falsefor the throwaway'sgit initalone_resolution_of'sgit submodule initwithouthooks_off_answers' twols-remotes run in the throwaway's own directorya-directory-holding-nothing; and first over the whole file with-xls-remote --get-urltherea-network: the NOTE of a url on a network not asked. It survived the first form of the row, and thea-networkcase was added for itls-remotetherea-directory-holding-nothing_answerstakes_split_clone_env(), a parent's-ctoo-x_answers'ls-remotes withouthooks_offls-remotereads no index and writes no ref, so it runs no hook and no fsmonitor program.Across the three rounds, 43 distinct mutants: 41 killed, and M15 and S4 survive as equivalent. Of r1119b's own list on
9b998936, R8 and A18 survived there and are killed now. R2 (no--shared, which changes only the cost), R3 (the clone after_paths_git_refuses, whose finding wins either way) and A17 (_resolvedtakes a parent's-c, under which the throwaway's own hook setting still wins) were judged no defect, and stay so.Gate
On
bba168bd, which merges main038dafba(#1091, #1111), with a clean tree: 18 batches, 3,413 passed, 0 failed, 8 skipped. The head since is195d9936, which changes one test file and nothing the tool reads; the rows it re-ran are named under Tests.The environment was a stub
gh,GH_TOKENbogus, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,LANES_LANEunset andEMAILexported. Each batch had its own basetemp with retentionfailed, each pytest call ran under 530 s, and at most 4 ran at once.test_adopt_check_throwaway_repos.py,test_adopt_relative_submodule_url.pyandtest_adopt_reencoded_files.py: 157.test_adopt_nested_attributes.pyandtest_adopt_empty_symlinks.py: 132.test_adopt_root_symlinks.py, in 2 shards: 205 passed, 3 skipped.test_adopt_symlink_target_bytes.py: 130.test_adopt_execute_refusals.py: 181.test_adopt_entry_paths.py,test_adopt_literal_names.py,test_adopt_project_is_the_source.py,test_adopt_split_branch.pyandtest_windows_paths.py: 209.test_adopt_submodules.py: 167.test_adopt_history_gitlinks.pyandtest_adopt_submodule_url_answers.py: 67.test_adopt_manifest_values.pyandtest_adopt_leg_path_collisions.py: 177.test_adopt_plan_yaml_quoting.py: 540.test_scaffold_leg_path_collisions.py,test_repo_hygiene.py,test_agents_md_codes.py,test_readme_windows_quoting_sentence.py,test_cli_reference.py,test_adopt_work_dir_refusal.py,test_adopt_clone_claimed.py,test_no_auto_maintenance.pyand main'stest_path_guard_hygiene.py: 520 passed, 2 skipped.test_adopt_c_locale_bytes.py,test_adopt_check_plan_name.py,test_adopt_pins.pyandtest_pin_annotated_tag.py: 243.test_adopt_shape_pin_and_work_dir.py, which names the work-dir NOTE,test_visibility_field_words.py,test_scaffold_reuse_seeded_legs.pyandtest_adopt_root_shape_paths.py, which the merge changed: 240.test_shape_doctor.py, in 4 shards of 112: 445 passed, 3 skipped.Before this round:
9b998936had 1 failure:test_no_auto_maintenance.py::test_no_test_sets_a_git_config_count_of_its_own(r1119b's MUST-1).4ed7c7bfgave 1,061 passed, 2 failed and 8 skipped: that row, and main's ref-probe row (MUST-2).0b326952, the head before the last docstring edit, gave the same 18 batches with the same counts.tests/test_path_guard_hygiene.py(tests/test_path_guard_hygiene.py and shape-doctor.py: a bare is_file/is_dir/exists guard in a shipped tool fails by file and line unless its_is_aor a reasoned allowlist covers it, and aproject.yamllinked into an unsearchable folder isyaml-unreadable(#430) #1111) passed on the merge itself, with no entry added: this PR adds no bareis_file,is_dir,exists,is_symlinkoros.pathguard.The merge of main
e0a98f13merges mainef619c74(#1158, which adds #1033's case-twin rows totests/test_adopt_check_throwaway_repos.py) into this branch by a merge commit, on top of195d9936. The merge base is main038dafba. The push fast-forwarded the branch, and nothing was rebased or forced.refused_paths'update-index -z --index-infois untested withcore.ignorecaseand case twins (aandA) on a case-insensitive disk #1033's second, with a blank line between. Every other hunk of the file merged without a conflict, and no other file conflicted.with_pathsgained two parameters,own_blobsandlinks, both with defaults. This PR's case-twin row,test_case_twins_are_both_held_under_core_ignorecase, calls it with neither, and so holds one shared blob as it did.blob_ofand adopt-project.py:refused_paths'update-index -z --index-infois untested withcore.ignorecaseand case twins (aandA) on a case-insensitive disk #1033'sCASE_TWINS,ALL_TWINS,TWINS_REFUSEDandTWIN_ANSWERS. This PR'sTWINSis a different name.--collect-only -qin a clean clone at each commit: 29 at038dafba, 49 at mainef619c74, 70 at195d9936, 90 at the merge.test_a_default_branch_git_will_not_take_stops_no_question, which it splits in two (see Tests).e0a98f13, each in the probe environment (a stubgh,GH_TOKENbogus, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAILexported,LANES_LANEunset):tests/test_adopt_check_throwaway_repos.py, one call: 89 passed, 1 skipped. The skip is main'stest_on_a_disk_that_ignores_case_each_twin_is_held_or_named, which needs a disk that ignores case, as its docstring says.tests/test_path_guard_hygiene.py,tests/test_no_auto_maintenance.pyandtests/test_adopt_single_repository_record.py, one call: 146 passed, 2 skipped. The two are rows oftest_no_auto_maintenance.pythat skip when the session began withGIT_CONFIG_GLOBALset, as the probe environment sets it.bba168bd. The merge brings in main's changes toscaffold-project.py,scripts/family.py, the v1.2.0 release notes and tests, and leavesadopt-project.pyas it was at195d9936; it imports none of those changed files.Known limits
What
check's clone may not inherit is a safety property, not a fidelity limit. Each variable_inherited_env()leaves out names another repository, its objects, an index or a work tree, and git writes there. UnderGIT_OBJECT_DIRECTORYnaming the source's own objects, a clone in the whole environment wroteobjects/info/alternatesinto the source (r1119), whichcheckpromises never to write to. The clone leaves them out, and a row pins it (M6 is killed)._answersand_resolvedleave them out too. UnderGIT_DIRnaming the source, in the whole environment:_answersread the source's own url rewrites (M14);_resolvedwrote[remote "origin"]and[submodule "u0"]into the source's config, andchecksaidplan ok(M14r).One row pins both.
The other throwaways still leave out
GIT_CONFIG_PARAMETERS, as adopt-project.pycheck's throwaway repositories: a SHA-256 source, a tree path git refuses, aTMPDIRon another disk and this machine's hooks change the answers #910 made them. That covers the rehearsal, adopt-project.py:checkfinds, andexecuterefuses before any leg, a kept nested.gitattributesthat outranks the root's line-ending rule for a pinned copy, and a root.gitattributesthatgit addwould re-encode (#530) #619'sgit add,_paths_git_refuses,_resolvedand_answers. Only the clone takes a parent git's-csettings now. Measured on9b998936with git 2.43:-c core.protectHFS=trueand a source path.g\u200cit/x:checksaysplan ok, andexecutestops extracting the spec leg (unable to checkout working tree) after both legs exist. The same setting inGIT_CONFIG_COUNTisplan-path-git-refuses.GIT_ATTR_SOURCE=bogus, which_inherited_env()leaves out:checksaysplan ok, andexecute's first clone stops at its checkout,fatal: bad --attr-source or GIT_ATTR_SOURCE, after both legs exist._AttributesProbe.__init__,_stored_as_writtenand_SplitRehearsal.__init__.With a work directory nothing can be made in,
checkno longer asks the url questions; 2a119f2 asked them inTMPDIR.adopt-work-dir-unwritableis the one finding, as for every throwaway since adopt-project.pycheck's throwaway repositories: a SHA-256 source, a tree path git refuses, aTMPDIRon another disk and this machine's hooks change the answers #910, andexecuterefuses it before it asks anything. Its NOTE says what went unasked: the source's paths and files, and the urls of the submodules the plan keeps. The relative-url finding needs no work directory, so the person meets it onechecklater (r1119's NIT-2).A partial-clone source is still fetched into by
check's own reads (r1119b's NIT-D, probe 10). The promisor fetch runs theupload-packprogram that the source's config names, and writes a promisor pack into the source. Main does the same, and the cause is not this PR's clone. It is filed as adopt-project.py check: a partial-clone source missing blobs that check reads is fetched into, so check's own reads run the upload-pack its config names and write a promisor pack into the source #1145.It was left out of v1.2.0 by Brett Heap's ruling of 2026-10-21T10:32:04Z. Lane 3 has since put it on milestone v1.2.1, and it lands under Brett Heap's v1.2.1 merge word.
Closes #1083
Closes #1032
Lane: openreposhape-1 (openRepoShape-1)
🤖 Generated with Claude Code
Summary by Sourcery
Align
adopt-project.py checkwith the repositories and environment that execution will use, surfacing clone and throwaway Git failures before any split work begins.New Features:
checkrehearse the split’s own source clone before evaluating paths or submodule URLs, reporting clone failures asadopt-git-unaskedbefore any leg is created.Bug Fixes:
Enhancements:
Documentation:
Tests: