Skip to content

scaffold-project.py: a partial-clone --pin-source short of the pinned commit or its trees is pin-source-unreadable, named a partial clone with git fetch --refetch --no-filter <remote> <commit>, and no longer fetched into (#1182) - #1223

Draft
brettheap wants to merge 11 commits into
mainfrom
fix/1182-scaffold-partial-pin-source

Conversation

@brettheap

@brettheap brettheap commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1182

Outside v1.2.0 by Brett Heap's ruling of 2026-10-21T10:32:04Z ("leave the follow-ups out of v1.2.0"). Measured against ef619c74a, the merge-base before the review round; main at e04826db4 is merged in since (5d90b9edb, a clean merge).

Review round (r1223: no MUST, one SHOULD, four NITs)

What

scaffold-project.py --pin <product>@<commit> --pin-source <clone> read the pin's digest with git in the clone (git cat-file -t of the commit, then git ls-tree -r). In a partial clone (git clone --filter) git fetches an object the clone lacks from its promisor remote on any read that needs it. Measured through the scaffold's own command line on ef619c74a and at this head (686a25253), git 2.43.0, the real run and --dry-run alike, with r1223's probe_nofetch.py and this PR's rows:

--pin-source base ef619c74a this branch
--filter=tree:0 --no-checkout (holds the pinned commit, none of its trees) exit 0, the clone's upload-pack ran 1 time, .promisor 1 -> 2 exit 2 pin-source-unreadable, 0 runs, .promisor unchanged, nothing made
--filter=blob:none --no-checkout lacking the pinned commit exit 0, 1 run, .promisor 1 -> 2 exit 2 pin-source-unreadable, 0 runs, unchanged, nothing made
blob:none holding the commit, checked out or not; a full clone; tree:0 checked out exit 0, 0 runs exit 0, 0 runs (unchanged)
a full clone lacking the commit pin-source-unreadable with git fetch --all first the same, word for word, with no partial-clone sentence

The refusal for a partial clone keeps the code pin-source-unreadable and the reader's own words, then says that the clone is a partial clone whose git fetches an object it lacks from its promisor remote (named), that the scaffold never fetches into a --pin-source, and names the cure: git fetch --refetch --no-filter origin <commit> in the clone, the pinned commit the reader could not read (one fetch per promisor remote, named in git's order). The cure as printed makes every short form readable: a tree:0 clone, checked out or bare, a blob:none clone that lacks the commit on another branch, and one that is merely behind. The scaffold run again reads the origin's own digest of that commit with 0 runs of its own.

The --pin-source help gains two sentences, the second in #1190's words: a partial clone is read where it holds the pinned commit and its trees; on a git that honours GIT_NO_LAZY_FETCH (2.45 or later, or a security release from 2.39.4 on), one git could read only by fetching what it lacks is pin-source-unreadable, and the scaffold fetches nothing into it; an older git fetches what it lacks into it. git v2.45.0's own Documentation/git.txt documents the variable. docs/cli.md is regenerated from the help.

Why

Tests

tests/test_scaffold_partial_pin_source.py, 40 rows, standalone (conftest's git, run_script, SCAFFOLD, make_source_repo, FILE_PROTOCOL). Each row clones a local origin (file://, no network, no gh) and sets remote.origin.uploadpack to a wrapper that logs each run, so "no fetch" is an answer the controls give too. Rows that need git to honour GIT_NO_LAZY_FETCH ask it first and skip, saying why, where it is ignored.

  • refusal x {tree:0, lacks-commit} x {dry-run, run}: exit 2, the partial-clone sentences, the cure naming the pinned commit, no git fetch --all and no refetch that names no commit, nothing made, 0 runs, objects/ unchanged;
  • a RELATIVE --pin-source openGlass (run in the folder holding it): the remediation names the resolved absolute path, not openGlass;
  • the cure as printed, run in a tree:0 clone and in a blob:none clone that lacks the commit (2 rows), lets the real run read the origin's digest of the pinned commit with no fetch of its own;
  • controls: a full clone, blob:none (checked out or not), a checked-out tree:0 clone, and a tree:0 clone made whole by fetch --refetch --no-filter, read the origin's digest with 0 runs; a full clone lacking the commit keeps git fetch --all; a folder in a partial clone is still pin-source-not-a-clone;
  • in process: main refuses a tree:0 clone with 0 runs, and with NO_LAZY_FETCH renamed to a name git does not read it fetches and exits 0 (so the variable is what stops the fetch); neither name is left in os.environ;
  • two promisor remotes: both named, one cure each, the reader's refusal as the cause;
  • unit rows: lazy_fetch_off (set inside; a prior 0 restored; an unset one removed; restored when the read raises), promisor_remotes (11 configs, including a boolean git itself dies on, and a folder that is not there), refetch_command (5 names, each with a commit), _remote_named (4 names).

Base vs head, the new file copied into a clean clone at ef619c74a: base 33 failed, 7 passed. The 7 are the 5 controls, the full clone lacking the commit and the folder row, which read the same on base; the 5 controls also pass at 961640c6 (5 passed), as their docstring says. Both cure rows fail on base at assert printed, because base prints no cure. Head 686a25253: 40 passed, none skipped (git 2.43.0 here honours the variable), and 40 passed under a 3.14-like sitecustomize shim, where pathlib's is_file, exists and is_dir answer False to any OSError.

Mutants of the new lines, one at a time, each run with the new file (S04 with tests/test_cli_reference.py), the file restored in finally and its blob checked equal to HEAD's after each: 42 (M01-M38 in shape_materialize.py, S01-S04 in scaffold-project.py). 41 killed, 1 survivor.

  • M37 (the clone named as typed, not resolved) survived the first 36 rows, as every row passed an absolute --pin-source; the relative-path row kills it (checked at af859e6a5).
  • SURVIVOR M08: _config_listed returns a failed run's stdout (return listed.stdout) instead of nothing. It is an equivalent mutant against real git: probed with git 2.43.0, git config -z --type=bool --get-regexp '^remote\..*\.promisor$' over remote.a.promisor=true, remote.b.promisor=maybe, remote.c.promisor=true (in each order of the three) exits 128 with fatal: bad boolean config value 'maybe' for 'remote.b.promisor' and writes NOTHING, because git collects every value before it writes one; a bad config line (fatal: bad config line) does the same. No config makes git 2.43 write part of a listing and then fail, so no row with a real git tells the two apart. A bad-boolean row was added to promisor_remotes' table anyway (a failed listing names nothing and nothing raises); it passes with or without the mutant. A stub git that writes a record and exits 128 would kill M08 (precedent: stub_git in tests/test_pinned_copy_text.py); not added, as it tests a git nobody has seen.
  • Batches A (M01-M20, 19/20) and B (M21-M38, S01-S04, 21/22) were run at 69fbca91a; M37 alone at af859e6a5 and M08 alone at cb4786860. They were not re-run at the merge commit.
  • r1223 ran 41 mutants of its own at 77687d685: 40 killed, and M8 survived, the same equivalent mutant.
  • This round, at 70babae7b, in a clone in the writer's scratch folder, with the file restored and its blob checked equal to HEAD's after each:
    • MA, the pasted cure drops the commit (git fetch --refetch --no-filter <remote> again): killed by 9 rows. test_the_cure_as_printed_makes_the_clone_readable[lacks-commit] kills it by behaviour: the scaffold re-run after the printed cure exits 2 (assert 2 == 0), while its [tree0] twin passes.
    • MB, the cure in words drops the commit: killed by the 3 refetch_command rows that say it in words.
    • 2 of 2 killed. After the restore, the cure rows pass (2 passed).

Gate

This round, at 686a25253

The same probe environment, with TMPDIR in the writer's scratch folder; one basetemp per call; worktree clean.

call files result
new tests/test_scaffold_partial_pin_source.py 40 passed in 18.40s
shim the same file, under r1223's 3.14-like sitecustomize shim (PYTHONPATH, shown to load) 40 passed in 17.13s
pins tests/test_adopt_pins.py::test_both_tools_read_a_pin_with_one_implementation, tests/test_scaffold_leg_path_collisions.py 105 passed in 5.38s
hygiene tests/test_path_guard_hygiene.py, tests/test_no_auto_maintenance.py 179 passed, 2 skipped in 29.90s (tests/test_no_auto_maintenance.py:222, x2, the reason below)
docs tests/test_agents_md_codes.py, tests/test_cli_reference.py, tests/test_repo_hygiene.py 171 passed in 39.36s; scripts/render-cli-reference.py --check: [ok] docs/cli.md is what the generator produces
base the new file at ef619c74a 33 failed, 7 passed in 16.35s

The 62-file gate below was NOT re-run at this head. It stands at 77687d685, before the merge of main e04826db4 (12 commits; scaffold-project.py and scripts/shape_materialize.py among them) and before this round's three commits.

The full gate, at 77687d685

Probe environment on every call (a stub gh first on PATH, a bogus GH_TOKEN, an empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, EMAIL set, env -u LANES_LANE), one basetemp per call, each call under 530 s, at most 2 at once, at head 77687d685 (the merge of main ef619c74a).

The checkpoint's gate list file was missing. It was rebuilt as the checkpoint's own command (grep -lE -- "--pin-source|pin_source|declared_pin_values" tests/*.py: 53 files at this head, with the new one) plus tests/test_scaffold*.py and tests/test_setup_project*.py (9 more): 62 files, 5692 tests. A first call of 20 of those files hit the 530 s limit (rc 124 with 27% run; no result), so they were run in 24 units of at most 260 tests.

COMPLETE: every call below ended with exit code 0, at 77687d685 (clean worktree), from 18:07Z to 18:51Z. The result column is pytest's own summary line, with -rfEs so each skip names its reason. One row per call; f00-f23 are the 24 units of the 62 files, d0-d3 the doctor's 4 shards.

call files result
hygiene tests/test_path_guard_hygiene.py, tests/test_no_auto_maintenance.py 107 passed, 2 skipped in 22.43s (tests/test_no_auto_maintenance.py:222, x2: "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one", which the probe environment does)
docs tests/test_agents_md_codes.py, tests/test_cli_reference.py, tests/test_repo_hygiene.py, tests/test_readme_windows_quoting_sentence.py 215 passed in 55.55s
f00 the new file (38 rows), test_scaffold_device_names, _e2e, _free_text_values, _ignored_shape_files 260 passed in 56.63s
f01 test_scaffold_init_empty_values, _leg_path_collisions, _leg_paths 167 passed in 21.63s
f02 test_scaffold_manifest_values, _mount_ignored, _pin_and_reuse 230 passed in 91.03s
f03 test_scaffold_pin_collisions, _referent_chain_links, _reuse_seeded_legs, test_setup_project_dry_run_offers 211 passed in 74.04s
f04 test_setup_project_name_edges 97 passed in 17.91s
f05 test_setup_project_py 340 passed in 109.62s
f06 test_setup_project_ref_parts, _repo_misdrawn, _typed_name, test_adopt_pins 221 passed in 165.74s
f07 test_chain_pin_sources, test_neutral_pin_validator, test_pin_annotated_tag, test_pin_source_env_collisions 259 passed in 121.02s
f08 test_pin_source_top_level, test_pin_source_unreadable, test_pinned_copy_text 216 passed in 65.76s
f09 test_validate_pins_named_and_spelled 344 passed in 58.04s
f10 test_validate_pins_spelled_and_named, _without_git, conftest.py 185 passed in 36.58s
f11 test_adopt_entry_paths, test_adopt_execute_refusals 260 passed in 278.42s
f12 test_adopt_finding_spelling, _ignored_shape_files, _manifest_values 247 passed in 143.30s
f13 test_adopt_plan, _plan_escapes, _plan_flag_refusals_first 214 passed, 1 skipped in 69.14s (tests/test_adopt_plan.py:567: SHAPE_MEDXEHR_CLONE is unset, or names no directory: the MedxEHR-backed plan test needs a read-only clone of MedxSoft/MedxEHR)
f14 test_adopt_plan_yaml_quoting (one file of 540 tests, run whole: over the 260 bound) 540 passed in 175.36s
f15 test_adopt_question_and_submodule_spelling 102 passed in 27.60s
f16 test_adopt_root_symlinks, test_adopt_spelled_names 232 passed, 3 skipped in 286.05s (tests/test_adopt_root_symlinks.py:1926, :1981, :2532: "the reftable backend needs git 2.45 or later"; this bench has git 2.43.0)
f17 test_adopt_template_values, test_assembly_root_unreadable_paths, test_chain_link_fold, test_chain_remedy_callers 245 passed in 84.73s
f18 test_clone_url_bytes, test_doctor_non_ascii_paths, test_doctor_validator_rows 227 passed in 200.82s
f19 test_doctor_validator_verdicts, test_dry_run_parity, test_github_name_limits, test_link_source_chain_defaults 252 passed in 125.98s
f20 test_naming_policy 264 passed in 17.21s
f21 test_non_utf8_name_helpers, test_repo_hygiene 120 passed in 2.69s
f22 test_setup_sh, test_stdout_encoding 238 passed, 1 skipped in 94.69s (tests/test_stdout_encoding.py:285: "the default this is about is Windows' own")
f23 test_template_text_placeholders, test_update_shape_landing 216 passed in 154.72s
d0 tests/test_shape_doctor.py, shard 0 of 4 (112 of 448 ids) 111 passed, 1 skipped in 177.84s (tests/test_shape_doctor.py:10510: "the PowerShell line is printed, and powershell.exe exists, only on a Windows host")
d1 tests/test_shape_doctor.py, shard 1 (112 ids) 112 passed in 166.29s
d2 tests/test_shape_doctor.py, shard 2 (112 ids) 111 passed, 1 skipped in 176.35s (the same line 10510, the same reason)
d3 tests/test_shape_doctor.py, shard 3 (112 ids) 111 passed, 1 skipped in 178.58s (the same line 10510, the same reason)

The 3 doctor skips are the 3 cases (family, manifest, pins) of test_a_template_validators_next_runs_when_pasted_into_powershell, a test marked skipif(os.name != "nt"): one in each of d0, d2 and d3.

Totals, summed from the logs:

  • f00-f23 (24 units, 62 files): 5687 passed, 5 skipped = 5692, the sum of the files' collected counts (conftest.py collects none).
  • d0-d3: 445 passed, 3 skipped = 448 ids, from one collect; the 4 shards are disjoint and their union is the whole list.
  • the 28 units: 6132 passed, 8 skipped = 6140.
  • with hygiene (107 passed, 2 skipped) and docs (215 passed): 6454 passed, 10 skipped = 6464 test runs. tests/test_repo_hygiene.py (101 tests) is in both docs and f21, so over distinct tests: 6353 passed, 10 skipped = 6363.
  • failed 0, errors 0, deselected 0, no unit re-run. The 10 skips are the 1 + 3 + 1 + 3 + 2 above, each with its reason printed.

Two notes on the record. The earlier call of 20 files (rc 124 at 530 s) is no result and is not in the table. f11 and f13 were still running when lane 1's LAST STEP stopped the scheduler; both ran to their end, and their .rc files were written by hand (0) after the logs read "260 passed" and "214 passed, 1 skipped": the pytest summaries above, not those two files, are their result.

Known limits

  • A git that ignores GIT_NO_LAZY_FETCH (older than 2.45.0 without the security releases of 2024-05-14, back to 2.39.4) fetches as before; the rows that need the variable skip there, saying so.
  • The cure fetches the pinned commit and every object it reaches, with no filter. For a blob:none clone that is merely behind, that is every blob of that commit's history, where git fetch --all would add one small pack and cure it too. Measured at this head with probe_nofetch.py: after the printed cure, 1 pack and 10 files under objects/; after fetch --all, 2 packs and 8. Later fetches stay filtered while the remote's partialclonefilter is set.
  • The fetch by id is measured over file:// (protocol v2) only, not against GitHub.
  • With two promisor remotes, the cure is one fetch from each, in git's order. A remote that lacks the commit was not measured.
  • git fetch --refetch needs git 2.36 or later; the remediation says so.
  • adopt-project.py plan is unchanged (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 handles plan's own read).
  • M08 survives, as above.

Not in this PR

Lane: openreposhape-1 (openRepoShape-1)

🤖 Generated with Claude Code

Summary by Sourcery

Refuse unreadable partial-clone pin sources without fetching into them, and provide a precise fetch command for recovery.

New Features:

  • Detect unreadable partial-clone pin sources without triggering lazy fetches and provide a commit-specific remediation command to make them readable.

Bug Fixes:

  • Prevent scaffold and dry-run operations from silently fetching missing pinned commits or trees into partial --pin-source clones.

Enhancements:

  • Preserve existing handling for full clones and partial clones that already contain the pinned commit and trees while supporting multiple promisor remotes.

Documentation:

  • Document partial-clone pin-source behavior, Git version requirements, and the no-fetch guarantee in the CLI reference.

Tests:

  • Add comprehensive coverage for partial-clone refusal, remediation, promisor-remote handling, environment restoration, and no-fetch behavior.

Brett Heap and others added 6 commits October 23, 2026 11:16
…e scaffold reads its pins with `GIT_NO_LAZY_FETCH=1`, so a partial-clone `--pin-source` short of the pinned commit or its trees is `pin-source-unreadable`, naming the partial clone and `git fetch --refetch --no-filter`, and is no longer fetched into (#1182)

Measured on main 961640c with git 2.43, through the scaffold's real run
and `--dry-run` alike: a `--filter=tree:0 --no-checkout` `--pin-source`,
and a `--filter=blob:none --no-checkout` one lacking the pinned commit,
each ran the clone's promisor `upload-pack` once (`.promisor` 1 -> 2) and
the run went on to exit 0. A `blob:none` clone holding the commit ran none.

Now `_declaration` reads the pins through `pin_values_unfetched`, which is
`declared_pin_values` under `lazy_fetch_off` (the environment put back
after), and which re-raises the reader's own `pin-source-unreadable` for
a partial clone (`promisor_remotes`, the doctor's #683 rule) with that
reason and the `git fetch --refetch --no-filter <remote>` cure. A clone
that is no partial one keeps the reader's refusal and its `git fetch
--all` remediation. Both forms now exit 2 with no `upload-pack` run and
nothing created. `--pin-source`'s help says so; docs/cli.md regenerated
with scripts/render-cli-reference.py. `adopt-project.py plan` is not
changed here.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the clone only for `pin-source-unreadable`, which only a `--pin-source` gives (#1182)

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…--pin-source`, each counting the clone's `upload-pack` runs: refused with none at head, fetched into once on base (#1182)

Each row clones a local origin (`make_source_repo`, `main` plus a `side`
commit, filters allowed) and sets `remote.origin.uploadpack` to a wrapper
that logs each run, so "no fetch" is an answer the controls give too.

- A `tree:0` clone holding the pinned commit and none of its trees, and a
  `blob:none` clone lacking the commit, `--dry-run` and the real run:
  `pin-source-unreadable` naming the partial clone, `origin` and `git
  fetch --refetch --no-filter origin`, no `git fetch --all`, nothing made,
  0 runs, `objects/` unchanged. On 961640c: exit 0 after 1 run each.
- The cure as printed, run in a `tree:0` clone, lets the real run read the
  origin's digest with no fetch of its own.
- Controls, passing on 961640c too: a full clone, a `blob:none` clone
  holding the commit (checked out or not) and a `tree:0` clone made whole
  read the origin's digest with 0 runs; a full clone lacking the commit
  keeps `git fetch --all`; a folder in a partial clone is still
  `pin-source-not-a-clone`, with no partial sentence.
- In process: `main` refuses a `tree:0` clone with 0 runs, and with
  `NO_LAZY_FETCH` renamed to a name git does not read, it fetches and
  exits 0; neither name is left in `os.environ`.
- `pin_values_unfetched` with two promisor remotes names both and gives
  one cure each, in the reader's words, with the reader's refusal as the
  cause.
- `lazy_fetch_off`, `promisor_remotes` (10 configs and a missing folder),
  `refetch_command` and `_remote_named`, one by one.

Rows needing git to honour `GIT_NO_LAZY_FETCH` ask it first and skip
where it is ignored. 36 pass at head; on 961640c the 6 controls pass and
the other 30 fail.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The clone is named by its resolved absolute path in the refusal's remediation, not as typed; a row with a relative --pin-source kills the mutant that names it as typed.

Lane: openreposhape-1 (openRepoShape-1)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… and raises nothing (#1182)

A remote.<name>.promisor of 'maybe' makes git config --type=bool --get-regexp exit 128 with nothing written (git 2.43 collects every value before it writes one), so only the partialclonefilter names a remote. The row passes with or without the returncode check in _config_listed: no config makes git 2.43 write part of a listing and then fail, so that check stays a named survivor.

Lane: openreposhape-1 (openRepoShape-1)

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

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

sourcery-ai Bot commented Oct 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR changes scaffold pin resolution to disable Git lazy fetching, detect partial-clone sources from configuration, and refuse unreadable sources with safe per-remote refetch instructions while preserving existing full-clone behavior; it also updates CLI documentation and adds extensive focused tests.

Sequence diagram for partial-clone pin-source reading

sequenceDiagram
    participant Scaffold
    participant Git
    participant Promisor
    Scaffold->>Git: declared_pin_values(args)
    Note over Scaffold,Git: GIT_NO_LAZY_FETCH=1
    Git-->>Scaffold: pin-source-unreadable
    Scaffold->>Git: promisor_remotes(clone)
    Git-->>Scaffold: promisor remote names
    Scaffold-->>Scaffold: Build safe refetch remediation
    Scaffold-->>Scaffold: Refuse without creating output
Loading

Flow diagram for unreadable partial pin-source remediation

flowchart TD
    A[Read pin values with lazy fetching disabled] --> B{Pin source readable?}
    B -->|Yes| C[Continue scaffold]
    B -->|No| D{Partial clone in config?}
    D -->|No| E[Preserve pin-source-unreadable refusal]
    D -->|Yes| F[Identify promisor remotes]
    F --> G[Print git fetch --refetch --no-filter per remote]
    G --> H[Refuse without creating output]
Loading

File-Level Changes

Change Details Files
Prevent scaffold pin reads from triggering lazy fetches in partial clones and provide targeted remediation.
  • Read declared pins under GIT_NO_LAZY_FETCH=1, restoring the environment afterward.
  • Detect promisor remotes from clone configuration without reading objects.
  • Extend pin-source-unreadable with partial-clone context and one safe git fetch --refetch --no-filter cure per promisor remote.
  • Preserve existing behavior and git fetch --all remediation for full clones.
scripts/shape_materialize.py
scaffold-project.py
Document the new partial-clone pin-source behavior in the CLI reference and help output.
  • Explain that partial clones are readable only when they contain the pinned commit and trees.
  • State that reads requiring a fetch are refused and the scaffold never fetches into --pin-source.
  • Regenerate the CLI documentation from the updated help text.
scaffold-project.py
docs/cli.md
Add comprehensive coverage for no-fetch behavior, remediation, configuration parsing, and compatibility controls.
  • Test filtered clones lacking commits or trees in both dry-run and real runs, including relative paths and multiple promisor remotes.
  • Verify full, blobless, and cured clones remain readable without scaffold-triggered fetches, while non-partial clone refusals remain unchanged.
  • Test environment restoration, remote-name quoting, Git-version behavior, object preservation, and no-created-output guarantees.
tests/test_scaffold_partial_pin_source.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1182 Prevent scaffold-project.py --pin-source from triggering lazy network fetches when reading pins from partial clones, including when the clone lacks the pinned commit or its trees. ✅
#1182 When a partial clone cannot supply the pin without fetching, refuse with pin-source-unreadable and explain that it is a partial clone, that the scaffold will not fetch into it, and how to make it readable. ✅
#1182 Add regression coverage for partial clones lacking the pinned commit or its trees, plus a control for a blob:none clone that already holds the commit. ✅

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-23T20:50:54Z) r1223, lane 1's independent reviewer (not the writer), reviewed #1223 at 77687d68: NOT READY, with no MUST, one SHOULD and four NITs. The fix itself holds. No run fetched into a partial-clone --pin-source, whether a dry run or a real one: 0 upload-pack runs and no new pack. Every control reads as it does on base, and 40 of 41 mutants are killed. M8 is equivalent on git 2.43, as the body says.

The full review

Review of PR #1223 (r1223, first independent review), head 77687d685

Verdict: NOT READY. One SHOULD finding and four NITs, no MUST. The fix itself is right: no run fetched into a partial-clone --pin-source, --dry-run or real run, and every control reads as on base. The SHOULD is about the remedy the refusal prints for one of the two forms the PR names.

Review checkout: /workspace/projects/openRepoShape-worktrees/l1-r1223, detached at 77687d68531650735b1ee1c34cea03ccdffd8f0a, still clean. Scratch folder: S=/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1223 (clones base at ef619c74a, and mut/mut2 at head). Every run used the probe environment (stub gh first on PATH, GH_TOKEN=bogus-r1223, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, EMAIL, no LANES_LANE), with git 2.43.0 and CPython 3.12.3.

Findings

SHOULD 1: for a partial clone that lacks the pinned commit, the printed cure does not make it readable. It also re-downloads every object, and it replaces the old git fetch --all advice, which did work where a fetched branch holds the commit.

  • Where: scripts/shape_materialize.py:4213-4219 (refetch_command) and :4264-4266 (the remediation). Also tests/test_scaffold_partial_pin_source.py:207, which asserts git fetch --all is absent for both forms, and :231, where only the tree:0 form ever runs the printed cure.
  • Evidence: $S/probe_text.py and $S/probe_commit_cure.py at head. The fixture is the PR's own: a --single-branch clone of main, pin on side. "state" is (upload-pack runs, packs, .promisor files, files under objects/).
    == lacks-commit cure 'fetch --refetch --no-filter origin': fetch rc=0 state after cure (1, 1, 1, 7) -> scaffold rc=2 ... REFUSED pin-source-unreadable
    == lacks-commit cure 'fetch --all': fetch rc=0 state after cure (1, 1, 1, 4) -> scaffold rc=2 ... REFUSED pin-source-unreadable
    == lacks-commit cure 'fetch origin <side>': fetch rc=0 state after cure (1, 2, 2, 8) -> scaffold rc=0
    tree0         refused rc=2; git fetch --refetch --no-filter origin <commit>   rc=0 -> re-run rc=0
    lacks-commit  refused rc=2; git fetch --refetch --no-filter origin <commit>   rc=0 -> re-run rc=0
    
    A blob:none clone that is merely behind (the pinned commit pushed to main after the clone) is cured by both commands. The printed one refetches every blob of history; git fetch --all keeps partialclonefilter=blob:none and adds one small pack.
  • Why it is not a MUST: the remedy's qualifier ("where a branch it fetches holds the pinned commit") keeps the text true, and the refusal offers other ways out (a full clone, or gh api). But the cure is the point of the PR, and the issue only asked for a cause to be named for the tree:0 form.
  • Fix: put the pinned commit in the cure, git fetch --refetch --no-filter <remote> <commit>. Measured above, it cures both forms. A promisor remote serves objects by id by definition, since that is how a lazy fetch asks. Or branch on git cat-file -e <commit> under lazy_fetch_off: name git fetch <remote> <commit> where the commit is absent, and the refetch only where the commit is present and its trees are not. Then add a row that runs the printed cure on lacks-commit, as :231 does for tree0.

NIT 1: the help says "the scaffold never fetches into it" without condition.

  • Where: scaffold-project.py:459-464 and docs/cli.md:411-415.
  • Evidence: the body's own Known limits: "A git that ignores GIT_NO_LAZY_FETCH ... fetches as before".
  • Fix: qualify it, e.g. "with a git that reads GIT_NO_LAZY_FETCH", then regenerate docs/cli.md.

NIT 2: the body measured base against head at 961640c6, but the PR's merge-base is ef619c74a.

  • Evidence: git diff --stat 961640c6 ef619c74a -- scaffold-project.py shows 198+/27-. I re-measured at the true merge-base: base 32 failed, 6 passed, head 38 passed, identical to the body.
  • Fix: name ef619c74a as the measured base.

NIT 3: Why says "#1190 keeps its own _lazy_fetch_off and _pins_read_unfetched", which reads as if they are on main.

NIT 4: Table row 3 ("tree:0 checked out | exit 0, 0 runs") has no row in the new file. KINDS has no checked-out tree:0.

  • Evidence: it does hold. $S/probe_rows.py gives base tree0-checkout rc=0 state (0, 3, 3, 12) -> (0, 3, 3, 12), and the same on head.
  • Fix: add "tree0-checkout": (("--filter=tree:0",), "main") to KINDS and to the controls' parametrize.

Mutant table

41 mutants of my own, one at a time, in a clone of the head ($S/mut, $S/mut2). Each ran pytest -x on the new file, S4 also with tests/test_cli_reference.py, and S5 also with test_adopt_pins.py::test_both_tools_read_a_pin_with_one_implementation and test_scaffold_leg_path_collisions.py. The file was restored in finally and its blob checked equal to HEAD's after each one. Both clones ended clean.

40 of 41 killed, 1 survivor (M8).

id mutant result (first failing row)
S1 _declaration reads with declared_pin_values again killed (refused[tree0-dry-run])
S2 reader="the tool" killed (refused[tree0-dry-run])
S3 nothing_made="" killed (refused[tree0-dry-run])
S4 help sentences dropped killed (test_cli_reference)
S5 declared_pin_values not imported into the scaffold killed (test_adopt_pins one-implementation)
M1 variable set to "0" killed (refused[tree0-dry-run])
M2 prior value forgotten killed (lazy_fetch_off[0])
M3 an unset variable not removed killed (in-process)
M4 a prior value removed, not restored killed (lazy_fetch_off[0])
M5 restored only on a normal exit killed (in-process)
M6 name hard-coded, not read from NO_LAZY_FETCH killed (in-process)
M7 _config_listed raises OSError killed (folder not there)
M8 a failed run's stdout returned (writer's M08) SURVIVED
M9 -z dropped killed (refused)
M10 git config run in the process cwd killed (refused)
M11 --type=bool dropped killed (promisor-yes)
M12 any promisor value counts killed (promisor-false)
M13 extensions.partialclone kept in config order killed (named-last)
M14 partialclonefilter alone not a promisor killed (filter)
M15 or changed to and killed (refused)
M16 no dedupe killed (refused)
M17 the named remote not moved last killed (named-last)
M18 the named remote first killed (named-last)
M19 subsection cut at the first dot killed (dotted-name)
M20 extensions.partialclone dropped from PROMISOR_KEYS killed (named-last)
M21 _remote_named ignores backticks killed (refetch[a-backtick])
M22 _remote_named ignores printability killed (refetch[unprintable])
M23 refetch_command pastes an option killed (an-option)
M24 refetch_command does not quote killed (spaced)
M25 refetch_command ignores backticks killed (a-backtick)
M26 no lazy_fetch_off (the fix undone) killed (refused)
M27 every refusal code asked about killed (folder in a partial clone)
M28 clone named as typed (writer's M37) killed (relative pin source)
M29 a clone with no promisor remote still called partial killed (full clone lacking the commit)
M30 from None killed (two promisor remotes)
M31 only the first remote's cure killed (two promisor remotes)
M32 only the first remote named killed (two promisor remotes)
M33 reader hard-coded in the detail killed (two promisor remotes)
M34 the reader's own detail dropped killed (two promisor remotes)
M35 nothing_made dropped killed (refused)
M36 PIN_SOURCE_UNREADABLE misspelt killed (refused)

M8 is equivalent on git 2.43, as the body says. $S/probe_m08.py ran both listings _config_listed makes over many configs. Not one wrote output and then failed:

  • a bad boolean, in all 6 orders, and in local, included, global, GIT_CONFIG_COUNT and GIT_CONFIG_PARAMETERS scope: bool rc=128 stdout=0B fatal: bad boolean config value 'maybe';
  • a bad config line: rc=128 stdout=0B for both listings;
  • a valueless extensions.partialclone: git itself segfaults (rc=-11 stdout=0B).

Measurements

  • Base against head (new file only). Head: 38 passed, rc 0. Base ef619c74a with the file copied in: 32 failed, 6 passed. The 6 are the 4 controls, the full clone lacking the commit and the folder row, all of which pass on both. The refusal, relative-path and cure rows fail on base by behaviour (assert 0 == 2, and no cure found). The unit rows fail with AttributeError, since the helpers are new.
  • No fetch ($S/probe_nofetch.py, which counts upload-pack runs, packs and .promisor files):
    • Base, every short form (tree0, lacks-commit, tree0-bare, behind), dry and real run: rc=0 runs 0->1 packs 1->2 promisor 1->2.
    • Head, the same forms: rc=2 runs 0->0 packs 1->1 promisor 1->1 made=[].
    • Accepted runs (blobnone, full) on both: rc=0 runs 0->0.
    • After the cure, a dry run and a real run left objects/ byte-identical. An earlier 13 to 10 file change came from git's background gc after the cure, not from the scaffold.
  • 3.14-like shim ($S/shim/sitecustomize.py; it was shown to load, and turns a PermissionError into False): the new file gives 38 passed.
  • Docs and codes:
    • scripts/render-cli-reference.py --check gives [ok] docs/cli.md is what the generator produces.
    • test_path_guard_hygiene, test_agents_md_codes, test_cli_reference and test_repo_hygiene give 220 passed. test_no_auto_maintenance gives 58 passed, 2 skipped (the probe environment's GIT_CONFIG_GLOBAL skip).
    • The PR reuses pin-source-unreadable and adds no code. The code test scans only AGENTS.md, so nothing new needs listing.
  • With main: main is 12 commits past ef619c74a; 50ce8ab touches scaffold-project.py and 0d76e4c touches shape_materialize.py. git merge-tree is clean (tree da415ced8). On that tree, the new file plus hygiene, cli-reference and codes give 229 passed, and the render check is ok.
  • Body claims checked and true:
    • exactly one closing form (Closes #1182); closingIssuesReferences = [1182]; title is 225 characters;
    • the stacking statement (merge-base ef619c74a);
    • table rows 1, 2 and 4 (stderr byte-identical on base and head), and row 3 (see NIT 4);
    • 38 rows: 11 promisor_remotes configs, 5 refetch_command names, 4 _remote_named names;
    • 53 + 9 = 62 gate files; collect-only gives 5692 tests collected, doctor 448, test_repo_hygiene 101;
    • the totals arithmetic (5687+5; 445+3; 6132+8; 6454+10; 6353 distinct);
    • the cited skip reasons;
    • scaffold.declared_pin_values is read by test_adopt_pins.py:1818 and test_scaffold_leg_path_collisions.py:438;
    • the promisor rule matches shape-doctor.py line for line.

Not covered

Lane: openreposhape-1 (openRepoShape-1)

Brett Heap and others added 4 commits October 23, 2026 21:16
Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t by its id, so a blob:none clone whose branches lack it is made readable too (#1182)

The printed cure was `git fetch --refetch --no-filter <remote>`, which
refetches only what the clone's branches hold: a `blob:none` clone of
`main` pinned on `side` was still `pin-source-unreadable` after it
(r1223 on #1223). Now `git fetch --refetch --no-filter <remote> <commit>`,
the commit the reader could not read, which `pin_digest_from_source`'s
refusal now carries. A row runs the printed cure on that form and re-runs
the scaffold to exit 0, as the `tree:0` row does.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…ch (#1182)

The body's table names a checked-out `tree:0` clone (exit 0, no
`upload-pack`), and no row held it (r1223 on #1223, NIT 4). It is now a
kind, `tree0-checkout`, and a control row beside the full and
`blob:none` clones.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap changed the title scaffold-project.py: a partial-clone --pin-source short of the pinned commit or its trees is pin-source-unreadable, named a partial clone with git fetch --refetch --no-filter <remote>, and no longer fetched into (#1182) scaffold-project.py: a partial-clone --pin-source short of the pinned commit or its trees is pin-source-unreadable, named a partial clone with git fetch --refetch --no-filter <remote> <commit>, and no longer fetched into (#1182) Oct 23, 2026
@brettheap

brettheap commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-23T21:57:04Z) r1223b, lane 1's independent re-checker (not the writer), re-checked #1223's fix round at 686a2525: READY, with no MUST, no SHOULD and two NITs that do not block.

The full re-check

Re-check of PR #1223 at 686a25253010d8ed (r1223b, answering r1223's review of 77687d685)

Verdict: READY. r1223's SHOULD-1 and NIT-1 to NIT-4 are all fixed and measured here. I found no MUST and no SHOULD. I did find two NITs of my own (below), which do not block.

  • Setup: review checkout /workspace/projects/openRepoShape-worktrees/l1-r1223b, detached at 686a25253010d8ed9f8c263d5b6cacdb574130c7, clean.
  • Scratch: S=/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1223b. It holds base (shared clone, now detached at 961640c68 with the new test file untracked) and mut (clone at head, clean).
  • Environment: every run used the probe environment (stub gh first on PATH, GH_TOKEN=bogus-r1223b, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, EMAIL, no LANES_LANE, TMPDIR in scratch), git 2.43.0, CPython 3.12.3.
  • Disk rule: df -h / and df -i / were checked before each pytest call by $S/gate.sh. Free space was 18G to 71G and inodes 86% to 88%. No call ran with fewer than 5G free or inodes above 90%.
  • gh: read-only GETs only.
  • State at LAST STEP: no run of mine is live (pgrep -fa r1223b is empty), so there is no PID to name. The two background runs, mergeA and mergeB, finished, and their logs are $S/logs/mergeA.log and $S/logs/mergeB.log.

1. SHOULD-1: the cure names the pinned commit

Source. scripts/shape_materialize.py:3661 sets refusal.commit = commit in pin_digest_from_source. refetch_command(remote, commit) at :4289 prints `git fetch --refetch --no-filter <remote> <commit>`. :4340 builds cures = ", then ".join(...), one per promisor remote. A remote name that does not paste is named in words, and the commit goes with it. The commit is parse_pin's lowercased 40 hex digits (:3604), so it pastes safely. Only pin_digest_from_source raises pin-source-unreadable on the declared_pin_values path, so exc.commit is always set.

Printed cure, run at head.

  • python3 -I /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1223/probe_nofetch.py $T $S/pn-head head. It extracts the cure from the refusal, runs it as printed, then re-runs the scaffold. Log: $S/logs/probe_nofetch_head.log.
    head tree0                dry rc=2 runs 0->0 packs 1->1 promisor 1->1 made=[]  REFUSED pin-source-unreadable
         cure: 'git fetch --refetch --no-filter origin 5f1a52cf...' -> state (2, 2, 2, 13)
    head tree0+cure           dry rc=0 runs 2->2
    head lacks-commit         dry rc=2 runs 0->0 packs 1->1 promisor 1->1 made=[]
         cure: 'git fetch --refetch --no-filter origin 62f799de...' -> state (1, 1, 1, 10)
    head lacks-commit+cure    dry rc=0 runs 1->1
    head tree0-bare+cure      dry rc=0 runs 1->1
    head behind+printed       dry rc=0 runs 1->1   (filter=blob:none kept)
    
    runs N->N means the scaffold's re-run ran no upload-pack.
  • python3 -I r1223/probe_commit_cure.py $T $S/cc-head. The earlier cure without the commit still fails for lacks-commit:
    tree0         ... origin                 rc=0 -> re-run rc=0
    tree0         ... origin <commit>        rc=0 -> re-run rc=0
    lacks-commit  ... origin                 rc=0 -> re-run rc=2 REFUSED pin-source-unreadable
    lacks-commit  ... origin <commit>        rc=0 -> re-run rc=0 state (1, 1, 1, 10)
    

Row. test_the_cure_as_printed_makes_the_clone_readable[lacks-commit] exists and passes. So does its [tree0] twin. Each row pulls the cure out of the refusal with re.search, runs it, re-runs the scaffold for real, checks the origin's digest, and checks the upload-pack count equals the count after the cure.

Mutant MA (pasted cure drops the commit), applied in $S/mut:

MA: rc=1 9 failed, 31 passed
  ...cure_as_printed_makes_the_clone_readable[lacks-commit]
  E assert 2 == 0   (the scaffold, re-run after the printed cure, still refuses)
  restored scripts/shape_materialize.py: blob equals HEAD (c295e89d19 vs c295e89d19); status=clean

[tree0] passes under MA, since the commit-less cure works for that form. Only [lacks-commit] catches it by behaviour. The other 8 failing rows are text rows: the 4 refusal rows, the relative-path row, the two-remote row and 2 refetch_command rows.

2. NIT-1: the help qualifier

3. NIT-2, NIT-3, NIT-4

4. The merge of main brought no change to the PR's own lines

  • git merge-tree --write-tree 77687d685 e04826db4 is da415ced80b0.... git rev-parse 5d90b9edb^{tree} is da415ced80b0..., and its parents are 77687d685 and e04826db4. git merge-base 686a25253 origin/main is e04826db4, which is still origin/main.
  • git diff --stat e04826db4 686a25253 shows exactly 4 files: docs/cli.md, scaffold-project.py, scripts/shape_materialize.py, tests/test_scaffold_partial_pin_source.py. The round's three commits (70babae7b, 4fc047abb, 686a25253) touch only those four, and the merge adds no file beyond the 12 main commits.
  • $S/interdiff.py compared the net +/- line multisets per file: (reviewed delta ef619c74a..77687d685) plus (round 5d90b9edb..686a25253) against (PR vs main e04826db4..686a25253):
    docs/cli.md                               now(+8/-1)    reviewed(+5/-1)    round(+6/-3)   unexplained 0
    scaffold-project.py                       now(+28/-7)   reviewed(+25/-7)   round(+7/-4)   unexplained 0
    scripts/shape_materialize.py              now(+190/-1)  reviewed(+175/-0)  round(+35/-21) unexplained 0
    tests/test_scaffold_partial_pin_source.py now(+474/-0)  reviewed(+451/-0)  round(+67/-44) unexplained 0
    TOTAL unexplained 0
    
    The one - line in shape_materialize.py that the reviewed delta lacks is raise Refusal( becoming refusal = Refusal( in pin_digest_from_source, which is the round's own change.
  • Main touched scaffold-project.py (+158/-) and shape_materialize.py (+126/-) in this range. Beyond the text merge, I ran the nearby suites at head:
    mergeA: test_pin_source_top_level, test_pinned_copy_text, test_pin_source_env_collisions, test_chain_pin_sources, test_pin_annotated_tag
            355 passed in 151.22s
    mergeB: test_scaffold_pin_and_reuse, test_scaffold_pin_collisions, test_scaffold_referent_chain_links, test_scaffold_reuse_seeded_legs
            286 passed in 133.67s
    

5. Claims in the body

All of these were measured and are true.

  • Closing form and metadata: exactly one closing form, Closes #1182. A regex over body and title finds only that. closingIssuesReferences is [1182]. Issue refs in the body are shape-doctor.py: run from a partial clone of the standard, the shape currency row fetches what the clone lacks, and with its remote out of reach calls each copy the standard changed since the pin both, so a doctor that fetches nothing fetches, and offline says DRIFTED of copies nobody edited #683, scaffold-project.py: a partial-clone --pin-source lacking the pinned commit or its trees is fetched into over the network #1182, 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. The title is 234 characters. The milestone is v1.2.1 and the PR is a DRAFT. Brett Heap's ruling of 2026-10-21T10:32:04Z is named. The body ends with the Lane: line, a blank line, then the 🤖 line.
  • Table: the four rows match the base and head probes above.
  • Rows: 40 rows, and the breakdown matches the file: 4 refusal, 1 relative, 2 cure, 5 control, 1 full-lacks, 1 folder, 1 in-process, 1 two-remote, 3 lazy_fetch_off, 11 promisor_remotes configs plus 1 folder-missing, 5 refetch_command, 4 _remote_named.
  • Base against head: the new file copied into $S/base at ef619c74a gives 33 failed, 7 passed. The 7 passing are the 5 controls (full, blobnone, blobnone-checkout, tree0-checkout, tree0-cured), the full clone lacking the commit, and the folder row. The two cure rows fail at assert printed. The refusal rows fail at assert 0 == 2. The unit rows fail with AttributeError. At main e04826db4 the same 33 rows fail. At 961640c68, the controls alone give 5 passed.
  • Round tests at head:
    • New file: 40 passed in 14.75s.
    • Under r1223's shim: 40 passed. The shim was shown to load (Path.is_file prints as _wrap.<locals>.guarded).
    • test_agents_md_codes, test_cli_reference and test_repo_hygiene: 171 passed.
    • test_path_guard_hygiene and test_no_auto_maintenance: 179 passed, 2 skipped.
    • test_adopt_pins::test_both_tools_read_a_pin_with_one_implementation and test_scaffold_leg_path_collisions: 105 passed.
    • test_pin_source_unreadable: 81 passed.
  • Mutants of the round's lines:
    • Results: 10 of 10 killed, from $S/mutate.py. The blob equals HEAD after each and both clones were clean at the end.
    • The writer's two, MA and MB, reproduce: MA killed by 9 rows, MB by the 3 refetch_command rows that say it in words.
    • My other mutants:
      • MC abbreviates refusal.commit to 12 characters: 8 rows.
      • MD cures only the first remote: 1 row.
      • ME drops --refetch from the pasted cure: 8 rows.
      • MF drops --no-filter from the pasted cure: 8 rows.
      • MG drops "(git 2.36 or later)": 5 rows.
      • MH changes ", then " to " and ": 1 row.
      • MI removes refusal.commit: 9 rows.
      • SA reverts the help sentence: 5 test_cli_reference.py rows.
    • The body's 42, 41-killed and M08-survivor bookkeeping is consistent: A is 19/20, B is 21/22, and M37 was killed later.
  • Known limits: after the printed cure objects/ holds 1 pack and 10 files, and after git fetch --all it holds 2 packs and 8 (head behind+printed (1,1,1,10), behind+fetch--all (1,2,2,8)). (git 2.36 or later) is printed. refetch_command's docstring says the same.
  • The gate it says it did not re-run: the body says the 62-file gate stands at 77687d685. I did not re-run it either (see below).

Findings

No MUST, no SHOULD. Two NITs of mine.

NIT A: a reviewer id sits in shipped code.

NIT B: refusal.commit is an attribute Refusal does not declare.

  • Where: set at scripts/shape_materialize.py:3661, read as exc.commit at :4340. Refusal.__init__ (scripts/repo_shape.py:171) takes only code, detail, remediation.
  • State today: the contract holds, because only pin_digest_from_source gives pin-source-unreadable on this path. A comment says so, and the tests cover it.
  • Risk: a second raiser of that code, or the follow-up that has plan call pin_values_unfetched, would hit AttributeError with nothing to catch it early.
  • Fix: read it with getattr(exc, "commit", None) and print a commit-less cure when it is missing. Or add commit: str = "" to Refusal. Not blocking.

Measurement, not a finding. The body's limit "a remote that lacks the commit was not measured" can now be stated. Probe $S/probe_tworemotes2.py: a tree:0 clone with two promisor remotes, the second serving an unrelated repo.

  • The cure for the remote that lacks the commit exits rc=128 fatal: ... not our ref <commit>. The other remote's cure exits 0 and leaves the scaffold's re-run at rc=0.
  • The same holds with the remotes in either order.
  • So "<cure 1>, then <cure 2>" is each remote that holds the commit, not a sequence that needs both. The body can say that. One probe artifact: after a failed fetch the clone was already readable, because git's own fetch lazily fetched from the other promisor remote.

What I did not cover

Lane: openreposhape-1 (openRepoShape-1)

… not a reviewer's id (#1182)

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

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-23T22:02:54Z) Lane 1's delta check of f50b4723: NIT A is fixed, and READY carries to f50b4723 as the new H0.

Lane: openreposhape-1 (openRepoShape-1)

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scaffold-project.py: a partial-clone --pin-source lacking the pinned commit or its trees is fetched into over the network

1 participant