Repository navigation
adopt-project.py: plan runs with GIT_NO_LAZY_FETCH=1, refuses a partial-clone --source that lacks objects as source-partial-clone, and names a partial --pin-source it could read only by fetching and the fetch that makes it whole (#1162) - #1190
brettheap wants to merge 34 commits into
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>
… 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>
….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>
…` and refuse a partial-clone source that lacks an object its branches or tags reach as `source-partial-clone`, so neither fetches into the source any more (#1145) Measured with git 2.43 on a `--filter=blob:none --no-checkout` source: `check` ran the source's promisor `upload-pack` 26 times and wrote 26 promisor packs into it; on this commit it runs it 0 times and leaves the source's `objects/` unchanged. A full clone and a partial clone given every object again (`git fetch --refetch --no-filter origin`) still check `plan ok`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rce` name `source-partial-clone`, the source it refuses, and the fetch that makes it whole (#1145) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e a partial-clone source that lacks objects as `source-partial-clone` and run no `upload-pack`, and read no commit the plan names that the source lacks (#1145) Each row counts the promisor's `upload-pack` runs through a counting `remote.origin.uploadpack` and hashes the source's `objects/`. On #1119's head bba168b, 18 of the 21 rows fail: `check` says `plan ok` for a `--no-checkout` partial clone, a checked-out one, and one whose own config sets `protocol.file.allow=always`; `execute` dies in `git filter-repo` after both legs; the guard alone is not there; and the plan's commit, read by the Triad advisory and by `_not_a_whole_clone`, is fetched once. The full clone, the cured clone and the cured `execute` pass on both. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fails is refused on any git, and not read as lacking nothing (#1145) The hand-written branch at a commit the source lacks makes `git rev-list --missing=print` exit 128 only before git 2.45; from 2.45 it lists the commit as lacking, so a `None` read as `0` would pass that row there. A walk made to fail in-process (`LACKING_WALK` with an option no git has) holds the `None` path on every git. It fails on bba168b (`plan ok`). Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…=1`, refuses a partial-clone `--source` that lacks objects as `source-partial-clone`, and says a partial `--pin-source` it could read only by fetching is a partial clone and how to make it whole (#1162) Measured on #1145's head 2c8082a with git 2.43: `plan` ran a `--filter=blob:none --no-checkout` source's promisor `upload-pack` 26 times (`.promisor` 1 -> 27), and once for a `--filter=tree:0` `--pin-source` and for a `--filter=blob:none` one lacking the pinned commit. Now `cmd_plan` is under `_lazy_fetch_off` (moved above it, since a decorator needs it at definition), asks `_refuse_a_partial_clone_short` right after `Source.of`, and `_pins_read_unfetched` re-raises the pin reader's `pin-source-unreadable` for a partial clone with that reason and the `git fetch --refetch --no-filter <remote>` cure. A blob:none pin clone that holds the commit is read as before. `--source` and `--pin-source` help say so; docs/cli.md regenerated with scripts/render-cli-reference.py. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e `--source` that lacks objects, and a partial `--pin-source` it could read only by fetching, and runs no `upload-pack` in either (#1162) Rows reuse #1145's `Source` (the counting `upload-pack`, the hash of `objects/`); a pin clone runs the same wrapper, so one log counts both. On base 2c8082a: 6 rows fail (the `--no-checkout` and checked-out partial `--source`, the guard alone, the `tree:0` and lacks-commit pin clones, the variable through a pin read), 8 controls pass (full and cured sources; full, blob:none, checked-out blob:none and cured tree:0 pin clones; a full pin clone lacking the commit; a folder inside a partial pin clone). On head all 14 pass. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leaves a `tree:0` pin clone short of its trees: measured with git 2.43, it fetched them (#1162) `git fetch --all` in a `--filter=tree:0 --no-checkout` clone at its tip ran the remote's `upload-pack` twice and left all 3 trees present, so `ls-tree -r HEAD` then read under `GIT_NO_LAZY_FETCH=1`. The comment now says why the reader's remediation fell short: it asks for a clone that has the pinned commit, and names no cause for a clone that holds it and lacks its trees. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blob the source lacks is refused through `check` and `execute` as `source-partial-clone`, and the remote `extensions.partialclone` names comes last where it is a promisor too, so neither the `--tags` of the walk nor that order is left unpinned (#1145) A cured partial clone that fetches an annotated tag with the filter it keeps lacks the blob its commit changes, and only the tag reaches it: branches lack 0 objects, tags 1. With `--tags` out of `LACKING_WALK`, `check` said `plan ok` and `execute` made both legs, then died in `git filter-repo`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ource-partial-clone` refusal says a later fetch stays filtered while `remote.<name>.partialclonefilter` is set, says git could not walk the refs, and names `git fsck`, where the walk fails, and the comments say `_not_a_whole_clone` reads a cloned plan's commit before the guard in any partial clone (#1145) The refetch the refusal names leaves `remote.<name>.partialclonefilter` as it was, so the next plain fetch can leave the source lacking again. Where `git rev-list` cannot walk the branches and tags, nothing is known of what is lacking: the refusal said "lacks what its branches and tags reach, as far as git can tell", and named a refetch that does not bring in a ref outside the remote's refspec. It now says git could not walk them and names `git fsck`, as `Source.tree` does, with the refetch as a second way. The code stays `source-partial-clone`, so the doc trio is unchanged. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
One conflict hunk in adopt-project.py per side, both resolved by taking both changes. `_lazy_fetch_off` moved above `cmd_plan` on this branch, and #1180 reworded its docstring where it stood: the reworded sentence now sits in the moved copy, ahead of this branch's pin-source sentence. The `Refusal` of `_refuse_a_partial_clone_short` keeps this branch's "`plan`, `check` and `execute` never fetch into the source" and takes #1180's `remediation`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR makes File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…ce.py: the `source-partial-clone` walk also reaches the source's HEAD where `git rev-parse --verify -q HEAD` gives an id, so a HEAD detached at a commit no branch or tag reaches is refused through `check` and `execute` and an unborn HEAD is read as before, and the `git fsck` sentence says what fsck names in a partial clone, and the count and walk-failure words are pinned (#1145) SHOULD-1 of r1180b: `LACKING_WALK` ended at `--branches --tags`, but `execute`'s `git clone` checks out the source's HEAD. HEAD is walked by its id, asked with `rev-parse --verify -q HEAD` and not `HEAD^{commit}`, which fetched a commit the clone lacks (measured with git 2.43, 1 `upload-pack` run) and exits 1 under `GIT_NO_LAZY_FETCH`. A plain `HEAD` fails on an unborn HEAD, exit 128, and on a file named HEAD in the work tree. NIT-1: the `None` remedy names what `git fsck` shows, a branch, tag or HEAD whose object is lacking, and no object a promisor remote holds. NIT-2 and NIT-3: the count arm's words and the failing walk's command, `exited`, the source's path after `git fsck` in, and the cure are asserted. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… blob the source lacks names HEAD in what its branches and tags reach, and a file of the source named HEAD is no path of the walk (#1145) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, with branches and tags, as what a partial clone must not lack (#1162) The guard `plan` asks walks the source's branches, tags and HEAD, as `check` and `execute` do, but the `plan --source` help said only branches or tags. The help now says "its branches, tags or HEAD reach", and docs/cli.md is regenerated from it with scripts/render-cli-reference.py. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…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>
…ource-partial-clone` walk ends with `--`, so a file of the source's work tree named as HEAD's id is no path of it (#1145) `_objects_lacking` walked `git rev-list --objects --missing=print --branches --tags <id>`, and a file of the source's work tree named as HEAD's id made git exit 128 (`ambiguous argument '<id>': both revision and filename`), so `check` and `execute` refused a source that lacks nothing as `source-partial-clone`. The walk now ends with `--`, after the id, so no file of the work tree is read as a path of it, and the walk the refusal names ends with `--` too. The new row puts a file named as HEAD's id in a cured partial clone's work tree and expects `plan ok` with nothing fetched; it fails at `b316f415` and passes here, and the failing-walk row, born and unborn, which asserts the walk as named, now expects the `--`. 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:32:09Z) Independent review r1190 of #1190 at
Lane 1 takes the 3 NITs in a fix round. PR #1190 review: PARTIAL, stopped at LAST STEP (18:17Z)Verdict so far: no MUST or SHOULD found; 3 NITs. I can't give READY yet, because the gate (step 5) was never run. Reviewed head What I coveredStep 1: reading the change. Correct.
Step 2: base against head. Matches the body.
Step 3: mutants. 13 of 14 killed.
Step 4: probes, all at head, 0
The HEAD-only case was "read from the code, not measured for Body claims checked, all true:
FindingsNIT 1: a missing order row (mutant M3 survives).
NIT 2: the
NIT 3: the pin cure leaves out the filter caveat.
What is left
No run of mine is live; every run was in the foreground and has ended. The review worktree and the mutant clone are clean. Files are in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1190:
Review worktree: /workspace/projects/openRepoShape-worktrees/l1-r1190 Lane: openreposhape-1 (openRepoShape-1) |
…xt beside the `source-partial-clone` walk says a plain `HEAD` fails on an unborn HEAD as `bad revision 'HEAD'` now that the walk ends with `--`, and the rows pin git's own words after the failed walk's exit and the exact count the source lacks (#1145) r1180d's NIT-1 to NIT-3. - NIT-1: the `HEAD_ID` comment, the `_objects_lacking` docstring and two row docstrings described the walk as it was before the `--`. Measured with git 2.43: `rev-list ... HEAD --` on an unborn HEAD says `fatal: bad revision 'HEAD'` (exit 128); a file named `HEAD` is read as a path (`both revision and filename`) only without the `--`. - NIT-2: the failing-walk row asks this git for its own words, running the same walk, and asserts `exited <rc>: <words>`. Git 2.43 prints its usage (exit 129) for `--no-such-option`, which does not name the option. Kills M15 (git's words dropped). - NIT-3: the count row counts the `?` lines of the tool's own walk (`--branches --tags HEAD --`), asserts more than 1, and asserts `that lacks at least <n> object(s) `. Kills M17 (count held at 1). Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….py: a row pins that `plan` refuses a partial `--source` before it reads a partial `--pin-source`, the `--pin-source` help says a partial pin clone is left unfetched only on a git that honours `GIT_NO_LAZY_FETCH`, and the pin refusal says a later fetch stays filtered (#1162) r1190's NIT-1 to NIT-3. - NIT-1: `test_a_partial_source_is_refused_before_a_partial_pin_source_is_read`, a `nocheckout` `--source` and a `tree:0` `--pin-source` together: `source-partial-clone`, no `pin-source-unreadable`, no plan, nothing fetched into either clone. Kills M3 (the guard moved after `_checked_plan_inputs`). - NIT-2: the `--pin-source` help names the gits that honour `GIT_NO_LAZY_FETCH` (2.45 or later, or a security release from 2.39.4 on) and says an older git fetches into the clone; `docs/cli.md` regenerated by `scripts/render-cli-reference.py`. - NIT-3: `_pins_read_unfetched`'s remediation ends with the source refusal's `_filter_key` sentence, and the pin-refusal row asserts it. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-1, 2026-10-23T20:50:03Z) Lane 1's delta check of #1190 since r1190's READY at
Lane: openreposhape-1 (openRepoShape-1) |
|
(lane openRepoShape-1, 2026-10-23T21:03:53Z) Lane 1 withdraws its NIT on the help's "2.45 or later" (issuecomment-6374465346): the text is right as written, so READY carries to
Lane: openreposhape-1 (openRepoShape-1) |
…nothing into a partial clone on a git that honours GIT_NO_LAZY_FETCH, and that an older git fetches (#1182) "the scaffold never fetches into it" held with no condition, where the PR's own known limits say a git that ignores `GIT_NO_LAZY_FETCH` fetches as before (r1223 on #1223, NIT 1). The sentence is #1190's: on a git that honours it (2.45 or later, or a security release from 2.39.4 on). docs/cli.md regenerated with scripts/render-cli-reference.py. Lane: openreposhape-1 (openRepoShape-1) 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>



Closes #1162
Stacked on #1145 (PR #1180), and lands after it. This branch is on #1180's head
a0e8cded, merged in by160c2dce0("Merge #1180's a0e8cde (restacked on main, r1180d's NITs) into #1190"), with no conflict. #1180 now targets main: after #1119 squash-landed asb7edd881, #1180 was restacked through three merges (#1119's final head9b856443, its squashb7edd881, then main50ce8ab1), anda0e8cdedfixed r1180d's NIT-1 to NIT-3 (comments, two docstrings and two rows' assertions; no flag, code path or help text). This PR's own changed lines againsta0e8cdedequal those ofgit diff -U0 a4ec51d2 7f3e1280, 496+/-lines, line for line, in the same three files. Before that, it was on #1180's heada4ec51d2, merged in by7f3e12802("Merge #1180's a4ec51d (the walk ends with --) into #1190"), after the earlier mergesd4d6e2a7aof #1180's headb316f415and00f959c61of its head6cd96b8b. Its three own commits were made on #1180's earlier head2c8082a2:cbb007528code and docs,5a12a033ctests,0d3089aefa comment fix. A fourth,91a937e76, made on the second merge, changesplan's--sourcehelp anddocs/cli.mdalone (item 5 below). The first merge had two conflict hunks inadopt-project.pyand the second had three, all resolved by taking both sides (below, under the reruns); the third had none. The second merge brings in #1180's two commits since6cd96b8b(41230f640,b316f4157) and nothing of this PR's. It changes three files against00f959c61(adopt-project.py,docs/cli.md,tests/test_adopt_partial_clone_source.py), and its changed lines aregit diff 6cd96b8b1 b316f415's, 323 of them, line for line, but for two places where this branch's own words stand: the ", and byplan(#1162)" in the docstring of_refuse_a_partial_clone_shortand the "plan,checkandexecute" in theRefusal's detail. The third merge,7f3e12802, brings in #1180's one commit sinceb316f415,a4ec51d2e, and nothing of this PR's: thesource-partial-clonewalk (_objects_lackinginadopt-project.py) and the command its refusal prints both end with--, so a file of the source's work tree named as HEAD's id is no path of the walk, andtests/test_adopt_partial_clone_source.pygains one row (38 rows now, 37 before) and has its docstring and expected walk text changed in one existing row.adopt-project.pychanges by 16 lines (11 added, 5 removed: a comment, a docstring and two lines of code). The merge had no conflict: its tree,6f740ccb7, equals the tree ofgit merge-tree --write-tree 91a937e76 a4ec51d2. It changes two files against91a937e76(adopt-project.py,tests/test_adopt_partial_clone_source.py;docs/cli.mdis the same), and its changed lines aregit diff b316f415 a4ec51d2's, 45 of them, line for line.Outside v1.2.0, by Brett Heap's ruling of 2026-10-21T10:32:04Z ("leave the follow-ups out of v1.2.0").
What
#1145 made
checkandexecutenever fetch into a partial-clone--source.planreads the same source with git, and each--pin's digest from--pin-sourcewithgit ls-tree -r, and was left as it was. In a partial clone (git clone --filter) git fetches an object the clone lacks from its promisor remote on any read that needs it, soplanmade network calls it never announced, and wrote into a clone the person only meant to read.Measured on #1145's head
2c8082a2, git 2.43.0, Python 3.12.3 (the promisor'supload-packreplaced by a counting stub):planof ...--source,--filter=blob:none --no-checkout(28 objects lacking)upload-packruns,.promisorfiles 1 -> 27source-partial-clone, no plan, 0 runs,objects/unchanged--source,--filter=blob:nonechecked out (2 history blobs lacking)checkandexecuterefuse it since #1145)source-partial-clone, no plan, 0 runs--source, full clone, or a partial clone cured bygit fetch --refetch --no-filter--pin-source,--filter=tree:0 --no-checkout.promisor1 -> 2, then the digest readpin-source-unreadable, with the partial-clone sentence and the cure, no plan, 0 runs, unchanged--pin-source,--filter=blob:nonethat lacks the pinned commit.promisor1 -> 2pin-source-unreadable, as above--pin-source, full clone;--filter=blob:nonethat holds the commit (checked out or not)--pin-source, a full clone that lacks the commit; a folder inside a partial clonepin-source-unreadable/pin-source-not-a-clone, the reader's wordsWhat changed, in
adopt-project.py:@_lazy_fetch_off()oncmd_plan.NO_LAZY_FETCHand_lazy_fetch_offmoved abovecmd_plan(a decorator needs its name at definition), into a section "plannever fetches into a partial clone either (adopt-project.py plan: a partial-clone--sourceis fetched into, ascheckandexecutewere before #1145 #1162)". Every gitplanstarts inheritsGIT_NO_LAZY_FETCH=1; the environment is put back afterwards._refuse_a_partial_clone_short(source)runs incmd_planright afterSource.of(...), as it does incheckandexecute. Its detail now says "plan,checkandexecutenever fetch into the source".plan's--sourcehelp namessource-partial-cloneand thegit fetch --refetch --no-filter <remote>cure._pins_read_unfetched(args)wrapsdeclared_pin_valuesin_checked_plan_inputs. Where the reader raisedpin-source-unreadableand_promisor_remotes(args.pin_source)names a remote, it re-raises the SAME codefrom exc, with the reader's detail kept and a sentence added (the clone is a partial clone, git fetches what it lacks on any read that needs it,plannever fetches into a--pin-source), and a remediation that says no plan was written and gives the cure.--pin-source's help says which partial clones are read and which are refused.docs/cli.mdregenerated withscripts/render-cli-reference.py.91a937e76:plan's--sourcehelp says a partial clone short of an object "its branches, tags or HEAD reach" is refused, where it said "its branches or tags" (4 lines ofadopt-project.py, 2 added and 2 removed), anddocs/cli.mdis regenerated from it: 13 lines, 7 added and 6 removed, all in that help's one paragraph, whose later lines only re-wrap (the words after the changed sentence are the same; argparse breakssource-partial-cloneat its hyphen, as it breakswork-dir-at line 983).scripts/render-cli-reference.py --checksays the file is what the generator produces. No behaviour changes.359a749c0(r1190's NIT-2):--pin-source's help no longer says a partial pin clone is refused "sinceplannever fetches into it" on every git. It says that on a git that honoursGIT_NO_LAZY_FETCH(2.45 or later, or a security release from 2.39.4 on) such a clone is refused aspin-source-unreadablewith nothing fetched into it, and that an older git fetches what it lacks into it, as Known limits says.docs/cli.mdis regenerated from it (3 lines out, 6 in).359a749c0(r1190's NIT-3): the remediation of_pins_read_unfetchedends with the source refusal's sentence, through the same_filter_key: "Later fetches stay filtered whileremote.origin.partialclonefilteris set, so the clone can lack objects again." A later pin to a newer commit in atree:0clone lacks its trees again.What #1180 changed since the first merge reaches
planthrough the one function it shares withcheckandexecute. At the merge headd4d6e2a7a,_refuse_a_partial_clone_shortwalks the source's HEAD as well as its branches and tags (_head_idandHEAD_ID: HEAD's id fromgit rev-parse --verify -q HEAD, which reads no object, and an unborn HEAD is read as before)._objects_lackingtakes that id, so the refusal says "branches, tags and HEAD", and itsgit fscksentence says what fsck names in a partial clone. The--sourcehelp ofcheckandexecute(and its lines indocs/cli.md) says "branches, tags or HEAD";plan's own--sourcehelp said "branches or tags" atd4d6e2a7aand says "branches, tags or HEAD" since91a937e76(item 5). Read from the code, not measured forplan: a source whose HEAD is detached at a commit no branch or tag reaches, and that lacks an object, is refused byplanas bycheckandexecute. This PR's 15 rows still pinplan's refusals for the branch and tag cases, and #1180's new HEAD rows runcheckandexecute. #1180's third commit,a4ec51d2e, ends the walk with--(the argv_objects_lackingruns, and the walk the refusal prints), so a file of the source's work tree named as HEAD's id no longer makes git exit 128 over a source that lacks nothing.planreaches it through the same function; that is read from the code, not measured forplan, as #1180's new row runscheck.Why
--pin-sourceis not refused for being partial. A digest is read from the pinned commit's trees, and a--filter=blob:noneclone that holds the commit holds them: measured, it gives the digest a full clone does, with 0 runs, checked out or not. Refusing it would break a working setup for no reason. Only a read that failed is asked about, so a partial clone that holds the commit and its trees is read as any clone is, and a clone that is no partial one keeps the reader's refusal untouched.pin_digest_from_source's remediation ("point --pin-source at a clone that HAS the pinned commit,git fetch --allfirst") names no cause for atree:0clone that holds the commit and lacks its trees. The sentence added says it is a partial clone, and the cure isgit fetch --refetch --no-filter <remote>there, or a full clone, or dropping--pin-source.shape_materialize.pyandscaffold-project.pyare not changed.checkandexecutehave them. The variable keeps every read unfetched (the pin read included: where the reader runsgit ls-tree, git stops at a missing object instead of fetching it). The guard refuses a--sourceshort of objects before any read, so a git that ignores the variable still never fetches into the source. A full clone is asked its config alone, which reads no object.planrefuses whatcheckwould refuse, before the plan exists. A plan written from a sourcecheckandexecutethen refuse sent the person through a plan they could not use.Tests
New file
tests/test_adopt_plan_partial_clone.py, 15 rows (14, and since359a749c0the order row, r1190's NIT-1). It imports #1145'sSource, its fixtures (adopter,made,planned_once,no_ambient_variable) andskip_where_git_ignores_the_variablefromtests/test_adopt_partial_clone_source.py, and counts the promisor'supload-packruns with the same stub.2c8082a20d3089aetest_plan_refuses_a_partial_clone_source_that_lacks_objects[nocheckout, checkout]source-partial-clone, no plan, 0 runs,objects/unchangedtest_plan_reads_a_full_clone_and_a_cured_partial_clone_as_before[full, cured]test_plan_s_guard_alone_refuses_where_git_ignores_the_variableNO_LAZY_FETCHpatched to a name no git reads: the guard alone refusestest_plan_reads_a_pin_clone_that_holds_the_commit_and_its_trees[full, blobnone, blobnone-checkout, tree0-cured]test_plan_refuses_a_partial_pin_source_it_could_read_only_by_fetching[tree0, lacks-commit]pin-source-unreadablenaming the partial clone and--refetch --no-filter, and (since359a749c0) that a later fetch stays filtered, 0 runs, pin clone unchangedtest_a_partial_source_is_refused_before_a_partial_pin_source_is_read(since359a749c0)nocheckout--sourceand atree:0--pin-sourcetogether:source-partial-clone, nopin-source-unreadable, no plan, nothing fetched into either clone, on any gita0e8cded)359a749c0)test_a_full_pin_clone_that_lacks_the_commit_is_refused_as_beforetest_a_folder_inside_a_partial_pin_clone_is_no_clone_as_beforepin-source-not-a-clone, no added sentencetest_the_variable_is_what_keeps_a_partial_pin_source_unfetchedtree:0pin clone: refused with nothing fetched; with the constant patched, the plan is written after a fetchBase: 6 failed, 8 passed. Head: 14 passed (9.8 s). No assertion reads text that depends on the Python version. Against #1180's new head
6cd96b8b(the new file alone, with none of this PR's code): the same 6 rows fail and the same 8 pass. At the merge heads00f959c61,d4d6e2a7a,7f3e12802and160c2dce0the 14 rows pass (call (a) of each rerun below). Against this PR's base now, #1180's heada0e8cded(itsadopt-project.pyanddocs/cli.mdput into a scratch clone of359a749c0, the 15-row file run there): 7 failed, 8 passed in 25.93 s, the same 6 and the order row, whoseplanwrites the plan. Head359a749c0: 15 passed.Mutants: 8 of 8 killed, no survivor (runner restores each in a
finally, clone of the tree). Run again at the first merge head00f959c61, and again at the second,d4d6e2a7a(a fresh clone each time;adopt-project.py's blob,d70f0dd4dthen745328f1f, checked equal to HEAD's after each mutant): 8 of 8 killed each time, by the same rows (the counts below are from the run atd4d6e2a7a, each one the same as at00f959c61):@_lazy_fetch_off()oncmd_plan..._the_variable_is_what_keeps_...(3 failed)cmd_plan..._the_variable_...(2 failed)Round 2 (r1190's NITs), 2 of 2 killed. r1190 ran 14 mutants against
7f3e12802and 13 were killed. Its survivor, here "r1190's M3", moves the guard after_checked_plan_inputs, so after the pin read._checked_plan_inputsreads no object of the source, so no fetch row could see the move. Each mutant below was applied to359a749c0'sadopt-project.pyin a scratch clone and the whole 15-row file was run. Then the file was restored, its blob checked equal to HEAD's889af8ffe, and the file run again (15 passed each time):_checked_plan_inputsplansaidREFUSED pin-source-unreadablewheresource-partial-cloneis assertedtree0andlacks-commitGate, on head
0d3089ae, git 2.43.0, Python 3.12.3, each call undertimeout 540with its own basetemp, at most two at once, under the probe environment (a stubgh, a bogus token,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAILset,LANES_LANEunset).plan's children now inheritGIT_NO_LAZY_FETCH=1; the files that dump a child's environment (test_setup_project_py.pyin H5,test_setup_sh.pyin H8,test_doctor_validator_rows.pyin H11,test_cli_reference.pyin G7) passed, and so didtest_shape_doctor.py(G2 to G5).test_path_guard_hygiene.py,test_no_auto_maintenance.pyGIT_CONFIG_GLOBALset), 23 stest_shape_doctor.py, 4 node-id shards of 112test_scaffold_leg_path_collisions.pytest_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.pytest_adopt_plan_yaml_quoting.py, 3 node-id shards of 180test_validate_pins_named_and_spelled.pytest_setup_project_py.pytest_naming_policy.py,test_chain_pin_sources.pytest_adopt_root_symlinks.py,test_validate_pins_spelled_and_named.pytest_setup_sh.py,test_adopt_finding_spelling.pytest_adopt_execute_refusals.py,test_pin_source_env_collisions.pytest_adopt_pins.py,test_template_text_placeholders.py,test_adopt_plan_out_checked.pytest_doctor_non_ascii_paths.py,test_dry_run_parity.py,test_doctor_validator_rows.pytest_scaffold_free_text_values.py,test_scaffold_manifest_values.py,test_adopt_plan_flag_refusals_first.pytest_adopt_question_and_submodule_spelling.py,test_pinned_copy_text.py,test_adopt_manifest_values.py,test_pin_source_top_level.pytest_assembly_root_unreadable_paths.py,test_pin_source_unreadable.py,test_adopt_entry_paths.py,test_adopt_template_values.pytest_family_refuse_up_front.py,test_validate_pins_without_git.py,test_scaffold_pin_and_reuse.py,test_update_shape_landing.pytest_chain_remedy_callers.py,test_adopt_plan.py,test_git_output_decoding.py,test_family_add_refusals.py,test_scaffold_device_names.pySHAPE_MEDXEHR_CLONEnamestest_github_name_limits.py,test_adopt_plan_escapes.py,test_stdout_encoding.py,test_adopt_plan_leg_path_help.pyand 3 more (7 files)test_reader_refusals_named.py,test_readme_windows_quoting_sentence.pyand 6 more (8 files)test_doctor_partial_clone.py,test_pin_annotated_tag.py, ...)test_adopt_partial_clone_source.py,test_adopt_plan_partial_clone.py,test_doctor_no_lazy_fetch.pyand 5 more6629 passed, 10 skipped, 0 failed in 27 calls. w1162b ran 14 calls: 3935 passed, 3 skipped. Lane 1 ran the other 13 at the same head after LAST STEP: 2694 passed, 7 skipped. Each call was under 9 minutes. H1 to H20 hold the 70 files that
grep -l -i -E 'partial|promisor|pin-source|pin_source' tests/*.pyfinds and three named ones (test_adopt_plan_leg_path_help.py,test_adopt_plan_out_checked.py,test_adopt_plan_value_order.py), lessconftest.py(it has no tests) and the two files G6 and G7 already run: 70 files, 5808 tests. The new file's own 14 rows passed alone (above) andtest_adopt_partial_clone_source.py's 22 are in H20.Reruns at the merge head
00f959c61. The merge changed two files against0d3089aef:adopt-project.py(thesource-partial-clonerefusal: its remediation now differs by whether git could walk the refs, and says a later fetch stays filtered whileremote.<name>.partialclonefilteris set; the new helper_filter_key; docstrings and comments) andtests/test_adopt_partial_clone_source.py(#1180's rows, 26 now, 22 before).docs/cli.md,--help,tests/test_adopt_plan_partial_clone.pyand every other file are the same as at0d3089aef. The two conflict hunks were_lazy_fetch_off, which this branch moved abovecmd_planand #1180 reworded where it stood (its sentence about_not_a_whole_cloneis now in the moved copy, ahead of this branch's pin-source sentence), and theRefusalof_refuse_a_partial_clone_short(this branch's "plan,checkandexecutenever fetch into the source", with #1180'sremediation). Same probe environment and limits as the gate above.00f959c610d3089aeftest_adopt_plan_partial_clone.py,test_adopt_partial_clone_source.pytest_adopt_partial_clone_source.py,test_update_shape_partial_rollback.py,test_adopt_plan_value_order.py,test_non_utf8_name_helpers.py,test_update_shape_merged_away.py,test_chain_link_fold.py,test_doctor_no_lazy_fetch.py,test_adopt_plan_partial_clone.py70f324859)test_path_guard_hygiene.py,test_no_auto_maintenance.py_lazy_fetch_off,_refuse_a_partial_clone_short,_filter_key)tests/finds only the two files of (a) andtest_doctor_no_lazy_fetch.py(in (b); its match is the doctor's ownlazy_fetch_offtest)test_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.pytest_scaffold_leg_path_collisions.pytest_shape_doctor.py, the same 4 node-id shardsadopt-project.pyReruns at the second merge head
d4d6e2a7a. The merge changed three files against00f959c61:adopt-project.py(_head_idandHEAD_ID;_objects_lackingwalks the id; the refusal's and thegit fscksentence's words; thecheckandexecute--sourcehelp),docs/cli.md(that help) andtests/test_adopt_partial_clone_source.py(#1180's new rows: 37 now, 26 before).tests/test_adopt_plan_partial_clone.pyand every other file are the same as at00f959c61. The three conflict hunks, all taken as both sides: theSOURCE_PARTIAL_CLONEcomment (this branch's layout, withNO_LAZY_FETCHabovecmd_plan, and #1180's "branches, tags or HEAD" words), the docstring of_refuse_a_partial_clone_short(#1180's text, with this branch's ", and byplan(#1162)"), and theRefusal's detail (#1180's new text, with "plan,checkandexecutenever fetch into the source"). Checked by parsing the merged file and #1180's: the raise and the docstring equal #1180's plus thoseplanwords. Same probe environment and limits as the gate above (git 2.43.0, Python 3.12.3,timeout 540per call, own basetemp, at most two calls at once, 22 to 25 GB free before each).d4d6e2a7a00f959c61test_adopt_plan_partial_clone.py,test_adopt_partial_clone_source.pytest_path_guard_hygiene.py,test_no_auto_maintenance.pytest_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.pytest_scaffold_leg_path_collisions.pytest_adopt_source_clone_record.py,test_doctor_partial_clone.py,test_pin_annotated_tag.pytest_shape_doctor.py, the same 4 node-id shardsadopt-project.py, a fresh clone1202 passed, 5 skipped, 0 failed in 10 calls at
d4d6e2a7a(the 5 skips: G1's 2, as above, and the PowerShell row, once in each of three doctor shards). Each call was under 9 minutes. (f) holds the files that name something the merge changed: a grep oftests/forsource-partial-clone,_objects_lacking,LACKING_WALK,_head_id,HEAD_ID,_refuse_a_partial_clone_short, "branches or tags", "lacks at least" and "refetch --no-filter" finds onlytest_adopt_partial_clone_source.pyandtest_adopt_plan_partial_clone.py(both in (a) and (b)) andtest_doctor_partial_clone.py(here: its_objects_lackingis the doctor's own), with two more that read a source's clone record and a pin's annotated tag. The doctor shards are the gate rules' for any change toadopt-project.py, and so is (e).Reruns at the help commit
91a937e76. It changes one help string inadopt-project.pyand the lines ofdocs/cli.mdmade from it; no test reads the old wording (a grep oftests/for "branches or", "or tags reach" and "branches or tags" finds nothing). Same probe environment and limits as the gate above (git 2.43.0, Python 3.12.3, own basetemp per call, 31 GB free).91a937e76d4d6e2a7atest_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.pytest_adopt_plan_partial_clone.pyReruns at the third merge head
7f3e12802(#1180'sa4ec51d2: the walk ends with--). Two calls, run at once, each in its own basetemp, under the probe environment of the gate above (a stubgh, a bogus token,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAILset,LANES_LANEunset), git 2.43.0, Python 3.12.3, 49 GB free and 3 other pytest runs in the container before the start. Each call was under 9 minutes.7f3e12802test_adopt_partial_clone_source.py,test_adopt_plan_partial_clone.pyd4d6e2a7atest_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.py91a937e76222 passed, 0 skipped, 0 failed in 2 calls at
7f3e12802. The one new row is among the 38.test_cli_reference.pypassed, sodocs/cli.mdis whatscripts/render-cli-reference.pyproduces and needed no regeneration; the merge does not changedocs/cli.md. A grep oftests/forsource-partial-clone,_objects_lacking,LACKING_WALK,_head_id,HEAD_ID,_refuse_a_partial_clone_short, "lacks at least" and "refetch --no-filter", run at this head, finds the same three files as atd4d6e2a7a: the two of (a), andtest_doctor_partial_clone.py(its_objects_lackingis the doctor's own; not rerun, since no function it reads is changed). None of the merge's 45 changed lines holdsis_file,is_dir,exists,is_symlinkoros.path.Reruns at the fourth merge head
160c2dce0and at359a749c0(r1190's NITs). Under the probe environment of the gate above (a stubgh, a bogus token, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAILset,LANES_LANEunset), git 2.43.0, Python 3.12.3, each call in its own basetemp, under 9 minutes, at most two at once, 45 GB or more free before each.160c2dce0test_adopt_plan_partial_clone.py,test_adopt_partial_clone_source.py359a749c0359a749c0test_agents_md_codes.py,test_cli_reference.py,test_repo_hygiene.py7f3e12802, 171 at160c2dce0and359a749c0; the one more comes in with main, through #1180's restack)a0e8cded's tool359a749c0scripts/render-cli-reference.pywrotedocs/cli.mdfrom the new help, and--checkthen said[ok] docs/cli.md is what the generator produces. Lane 1 runs the full landing gate. H1 to H20, G1 to G6 and the 8 mutants of round 1 did not run at either head.The rest of the gate ran at
0d3089aef, before any merge: G1 to G7 and H1 to H20. Of those, G1 to G7 and H20 ran again at each of the first two merge heads (above). H1 to H19 were not rerun at any merge head, nor at91a937e76or7f3e12802, where only the calls of the two tables above ran: G1, G2 to G6, H20 and the 8 mutants were not rerun at either of those two heads. The merges brought in nothing but #1180's changes, which reach only a source that is a partial clone (_objects_lackingand_head_idare called from_refuse_a_partial_clone_shortalone, after it has returned for a clone with no promisor remote) and thecheckandexecute--sourcehelp text, and, at7f3e12802, the--the walk ends with (a change in_objects_lackingalone, which none of the 8 mutants is in); and that grep finds none of H1 to H19's files buttest_doctor_partial_clone.py(H19), which (f) reran atd4d6e2a7aand nothing has run since.Known limits
GIT_NO_LAZY_FETCHstill fetches into a partial--pin-source, as the--pin-sourcehelp now says too. Git honours the variable from 2.45.0 and in the security releases of 2024-05-14 back to 2.39.4 (CVE-2024-32465; Ubuntu's 2.43.0-1ubuntu7.1 and later). Before that, and in unpatched 2.40-2.44, a partial--pin-sourceshort of the pinned commit or its trees is fetched into, as before adopt-project.py plan: a partial-clone--sourceis fetched into, ascheckandexecutewere before #1145 #1162. There is no guard for pins, by design: a clone that holds the commit and its trees must stay readable. The--sourceis kept whole on every git, by the guard.--pin-sourcethat git cannot read for another reason than a missing object keeps the reader's own refusal, with the partial-clone sentence added, because the clone is partial; the cure it names is then not what fails it. The reader's own words come first in the detail.plandoes not walk a--sourcethat is not a partial clone: the guard reads its config alone.Not in this PR
scaffold-project.py --pin-sourcereads its pin with the samedeclared_pin_valuesand has the same fetch. Lane 1 files that as its own follow-up, outside v1.2.0 by the same ruling; the scaffold is not changed here.Lane: openreposhape-1 (openRepoShape-1)
🤖 Generated with Claude Code
Summary by Sourcery
Prevent planning from fetching into partial clones while preserving support for partial pin sources that already contain the required objects.
New Features:
planfrom lazily fetching missing objects into partial-clone sources and refuse incomplete sources before generating a plan.--pin-sourceclones while clearly refusing pin sources that require fetching and providing remediation guidance.Bug Fixes:
Enhancements:
plan,check, andexecute.Documentation:
Tests: