Repository navigation
shape-doctor.py: the partial-clone row's NEXT fetches each commit it lacks objects at, by id, from the promisor remote whose branches hold it, so it no longer loops where the pinned commit sits only on another remote or another branch, and its why says where the clone does not hold the commit at all (#754) - #773
Conversation
…lacks objects at, by id, from the promisor remote whose branches hold it, so it no longer loops where the pinned commit sits only on another remote or another branch, and its `why` says where the clone does not hold the commit at all (#754) RV-f's review of #742, measured with git 2.43: - S2: the NEXT was `fetch --refetch --no-filter <the first promisor remote>`, the remote whole. With the pinned commit on a second promisor remote's branches only, it exited 0 and the row came back the same; in a `--single-branch` clone whose pin is on another branch, the clone still did not hold the commit afterwards. The NEXT now names each lacking commit by id, and `remote_holding` picks the promisor remote whose tracking refs hold them all (`for-each-ref --contains`, which reads commits alone and fetches nothing). Where none does -- always for a commit the clone does not hold -- it asks the first, and the `why` names every promisor remote. - S1: where `objects_lacking` is None (the clone does not hold the commit), the `why` says so. - N1: the reason says "at least", since a missing tree hides what is under it from `rev-list`. - N2: the `why` says `--refetch` needs git 2.36 or later. tests/test_doctor_partial_clone.py: a `--single-branch` partial clone of a standard whose `main` lacks C1 (exit 3, `lacking == {C1: None}`, C1 still not held), its NEXT run as printed, two promisor remotes with C1 on the second's branches only, and the NEXT and "at least" of the existing case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUpdates partial-clone diagnosis and remediation so the generated NEXT fetches each missing commit by ID from a promisor remote that can provide it, avoiding loops caused by single-branch or multi-remote layouts while improving absent-commit and lower-bound object-count explanations. Sequence diagram for partial-clone diagnosis and targeted fetchsequenceDiagram
participant Doctor as shape-doctor.py
participant Clone as Partial clone
participant RemoteRefs as Tracking refs
participant Remote as Promisor remote
Doctor->>Clone: objects_lacking(shape, commit)
Clone-->>Doctor: Missing object count or None
alt Commit is present
Doctor->>RemoteRefs: for-each-ref --contains=commit
RemoteRefs-->>Doctor: Remote holding all lacking commits
else Commit is absent
Doctor->>Doctor: Select first promisor remote
end
Doctor-->>Clone: NEXT: fetch --refetch --no-filter remote commit IDs
Clone->>Remote: Fetch each lacking commit by ID
Remote-->>Clone: Missing objects for those commits
Flow diagram for partial-clone remediation selectionflowchart TD
A[Partial clone has missing objects] --> B{Pinned commit present?}
B -->|Yes| C[Check tracking refs with for-each-ref --contains]
C --> D{Promisor remote holds all lacking commits?}
D -->|Yes| E[Use that remote]
D -->|No| F[Use first promisor remote]
B -->|No| F
E --> G[Generate fetch command with each lacking commit ID]
F --> G
G --> H[User runs --refetch --no-filter]
H --> I[Doctor can compare copies on the next run]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…hold as absent on every git, so from git 2.45 on it is no longer counted as one object lacked and handed to `for-each-ref --contains`, which fetches it (#754, round 2) PR #773's CI, on git 2.55.0, failed three tests at setup with "the clone holds C1". The clone did not hold it. From git 2.45 on, `rev-list --missing=print` takes a missing tip as missing too: it prints `?<commit>` and exits 0 (git's rev-list documentation since v2.45.0, "If some tips passed to the traversal are missing"), where git 2.43, this machine's, refuses it, `bad object`, exit 128. The tests' `holds` read exit 0 as "held". The doctor read the same command. On git 2.45 and later `objects_lacking` counted the absent commit as one object lacked, not None, so the row said "lacks at least 1 object" where the clone does not hold the commit at all, and `remote_holding` asked `for-each-ref --contains` of a commit the clone lacks, a read that fetches it. A `?<commit>` line for the commit itself is now None, as the refusal is. tests/test_doctor_partial_clone.py: `holds` takes both answers as "not held"; `elsewhere` puts C1 on its `pinned` branch only once `single_branch`'s clone exists, so no clone flag of any git can bring it in, and the precondition stays; `objects_lacking` is held to all four answers it can get, the two for a missing commit included, through the module, since one machine has one git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T05:08:42Z) Independent review of #773 by RV-f (opus), rounds 1 and 2, head #754's claims (git 2.43 here) are all fixed at the head: S1, S2, N1 ("at least") and N2 (git 2.36). The head's tests against The round-2 cause is confirmed from git's source and docs.
Main (
The NEXT run as printed (doctor, then NEXT, then doctor). These converge to
Two cases end in SHOULDs:
NITs:
Mutants:
Tests:
Merges: clean with Lane: openreposhape-3 (openRepoShape-3) |
…re on different remotes' branches, the NEXT fetches from the remote holding the pinned commit only the commits that remote holds, and the `why` says which remote holds which, so the fetch no longer fails `not our ref` and comes back unchanged (#754, round 3) RV-f's review of #773 rounds 1 and 2 (SHOULD S2): a partial clone whose HEAD lacked objects on `origin`'s branches alone, with the pinned commit on `second`'s alone, got `fetch --refetch --no-filter origin <pinned> <HEAD>`. git refuses a fetch by id whole where the remote lacks one id, so the row came back the same, its `why` saying no branch held them. - `partial_clone_fetch` picks the remote and the commits: the first remote whose branches hold every lacking commit some remote holds, else the first holding the pinned commit, and only the commits that remote holds. The doctor run again names the rest. Where no remote's branches hold any, it is the first promisor remote and every commit, as before. - The `why` says where each commit is (`where_lacked`). Where the remote is a guess, it spells out the same fetch from each other promisor remote as a line to paste (N1). - `remotes_holding` (was `remote_holding`) lists every remote whose branches hold one commit, and is still never asked of a commit the clone does not hold. - SonarCloud S3776 and S3358 (S1): the reason, the `why` and the fetch are built by helpers, with no nested conditional, and the nested `def which` is gone (N2). Tests: `test_commits_on_two_remotes_are_fetched_from_each_in_turn` runs RV-f's case's NEXT twice to an answer, and `test_the_why_spells_out_the_fetch_from_each_other_promisor_remote` runs the `why`'s command for a never-fetched `second`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T08:54:04Z) RV-f's re-verification of #773 round 3 ( S2, RV-f's sparse scenario, run to convergence.
Run 1's WHY ("the pinned commit … is on second's branches; this standard's commit … is on origin's branches; so fetch … from second …, then run the doctor again") matches N1: with X absent from the clone and
Mutants:
Tests and merges:
Lane: openreposhape-3 (openRepoShape-3) |
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 3 days and 14 hours by commenting |
…ETCH=1`, so a read of an object a partial clone lacks fails rather than fetches, and `pin_restore` names the fetch where a root's partial clone lacks its pin's history (#787) (#791) Closes #787. The doctor must not fetch from a partial clone, but each read was guarded one by one. Now `main` in `shape-doctor.py` runs under `lazy_fetch_off`, which sets `GIT_NO_LAZY_FETCH=1` and puts back what was there. Every git the doctor starts, including `update-shape.py`'s `Upstream` reads and the validators `run_validator` starts, fails a read of an object a partial clone lacks rather than fetching it. No printed line carries the variable, so a pasted `fetch --refetch --no-filter` still fetches. git 2.45 and later reads the variable, as do the security releases of 2024-05-14 back to 2.39.4; an older git ignores it, and the older guards still hold. Before, a partial clone of a root whose HEAD commit broke its pin made the doctor fetch while `pin_restore` walked the pin's history. With the variable alone, that walk would read as "no pin there", so `pin_restore` has a new answer, `partial`: where the root is a partial clone that lacks the pin's history, the NEXT is `git -C <root> fetch --refetch --no-filter <first promisor remote>`, and the `why` says the pin's history, HEAD's included, is not all in this clone, and spells out the same fetch from each other promisor remote. The row stays a FINDING. `tests/test_doctor_no_lazy_fetch.py` is new: every row from a partial clone of the standard starts no fetch, the partial-root cases, and a recording `git` first on PATH shows that every git the run starts has the variable. It was stacked on #773 (#754), and its own change is `296a6dd6..1d774ab`. Lane 3's independent reviewer RV-f (opus) found it READY in two rounds: on `152e9c39` and `19665420`, over #773's `296a6dd6` (#791 issuecomment-6232803699), and on the head `1d774ab3`, a fast-forward of `19665420` (-6233631070). After #773 landed, it was restacked onto #773's squash by three merges, and its changed lines equal that delta (#791 issuecomment-6245870763). It lands on Brett Heap's word "merge lane 3's v1.1.1 drafts after the v1.1.0 tag" (2026-10-17T13:00:57Z). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #754 (comment)
Closes #754.
Lands after the v1.1.0 tag. (#742, the issue's "Lands after", is in main as
defc0fa3.)Round 3 (head
296a6dd6, on155db31f)RV-f's review of rounds 1 and 2 (comment) was READY, with two SHOULDs and three NITs. This round takes S1, S2, N1 and N2. N3 (
GIT_NO_LAZY_FETCH) is left for lane 3 to file. Every anchor in this body is now this head's.S2: commits on different remotes.
origin's branches alone, and the pinned commit was onsecond's alone.remote_holdingfound no remote holding both, so the NEXT wasfetch --refetch --no-filter origin <pinned> <HEAD>. git refuses that whole (not our ref), and thewhysaid no branch held them.partial_clone_fetch(shape-doctor.py:4656) picks the remote withfetch_source(:4697, called at:4685). It takes the first remote whose branches hold every lacking commit some remote holds, else the first that holds the pinned commit. The NEXT names only the commits that remote's branches hold (:4686), and the doctor run again names the rest, so the case converges in two runs.whysays where each commit is (where_lacked,:4709). For example: "the pinned commit … is on second's branches; this standard's commit … is on origin's branches; so fetch every object at the pinned commit … from second (…), then run the doctor again, which names the fetch for this standard's commit …".remote_holdingis nowremotes_holding(:4761): every remote whose branches hold one commit. It is still never asked of a commit the clone does not hold (:4682).N1: where the remote is a guess and other promisor remotes exist, the
whyspells out the same fetch from each, as a line to paste (fetch_said,:4731). For example: "…so fetch it by id from origin, or, where origin does not have it, from second:git -C … fetch --refetch --no-filter second <C1>".S1 (SonarCloud) and N2: these parts are now helpers, with no nested conditional:
lacking_said,:4633);lacked_commit,:4624);refetch_command,:4649);why(:4709,:4722,fetched_named:4739,then_said:4749).The nested
def whichis gone. SonarCloud's analysis of296a6dd6:python:S3776andpython:S3358are CLOSED (FIXED);Tests (
tests/test_doctor_partial_clone.py):second_promisor(:295) addssecondas a promisor remote, fetched or not.two_promisors(:308) now uses it.split_across_remotes(:333) is RV-f's S2 case.originafter the clone and fetched under the filter, so it lacks a blob that onlyorigin's branches hold. C1 is onsecond's branches alone.scripts/,templates/andcontracts/out of the working tree.test_commits_on_two_remotes_are_fetched_from_each_in_turn(:357):second C1, and thewhysays which remote holds which.origin <HEAD>.never_fetched(:381) andtest_the_why_spells_out_the_fetch_from_each_other_promisor_remote(:393): C1 is only onsecond, never fetched. The NEXT asksorigin, and thewhy'ssecondcommand, run as printed, lets the doctor answer.Mutants (each run on this head's code against
tests/test_doctor_partial_clone.py):155db31f'sshape-doctor.py… origin <C1> <HEAD>) and the N1 test (no command in thewhy)whywhydrops which remote holds which--containsasked of a commit the clone does not holdResults, on
296a6dd6, with the same stubghand command as below:tests/test_doctor_partial_clone.py,tests/test_update_shape_merged_away.py,tests/test_repo_hygiene.pyandtests/test_cli_reference.py, together:143 passed in 71.10s (0:01:11).tests/test_shape_doctor.py:436 passed, 3 skipped, 1 xfailed in 302.19s (0:05:02).tests/test_doctor_pin_restore_merges.pyandtests/test_doctor_next_is_a_command.py:73 passed in 61.77s (0:01:01).296a6dd6, run 39006467231, git 2.55.0:testsgives11542 passed, 144 skipped, 1 xfailed in 3099.12s (0:51:39).tests-git-bash,packageand SonarCloud pass.Round 2 (head
155db31f, oneb0568ac)Cause. CI's Linux job (run 38989174052) used git 2.55.0, and three tests failed at setup with "the clone holds C1". The clone did not hold C1; the probe misread it.
rev-list --missing=printtakes a missing tip as missing. It prints?<commit>and exits 0. git's rev-list documentation says so from v2.45.0: "If some tips passed to the traversal are missing, they will be considered as missing too, and the traversal will ignore them."bad object, exit 128.holdsread exit 0 as "held".The same misreading was in the doctor. On git 2.45 and later:
objects_lackingcounted an absent commit as 1 object lacked, not None.remote_holding(remotes_holdingsince round 3) ranfor-each-ref --containson a commit the clone lacks, a read that fetches it. CI never reached this, because the fixtures failed first.Changes:
shape-doctor.py:4542:objects_lackingreturns None whererev-listprints?<commit>for the commit itself, as it does whererev-listrefuses. The docstring says why at:4523.tests/test_doctor_partial_clone.py:215:holdstakes both answers as "not held". It usesrev-list, notcat-file -e, becausecat-file -ereads the commit and so fetches it wherever git does not honourGIT_NO_LAZY_FETCH.:233,:251:elsewherenow puts C1 on itspinnedbranch only oncesingle_branch's clone exists, so no clone flag of any git can bring C1 in. The preconditionassert not holds(clone, C1)stays, so a future change fails loudly at setup.:512,:525:test_objects_lacking_reads_a_missing_commit_on_every_gitgivesobjects_lackingeach of the four answersrev-listcan give: a missing commit before 2.45, a missing commit from 2.45 on, and a held commit with and without missing objects. It passes them through the module rather than running git, since one machine has one git.Proof:
eb0568ac'sobjects_lacking, the unit test fails the "from git 2.45 on" case only:1 failed, 3 passed.secondand C1, and run as printed it ends the row.Results, on
155db31f:tests/test_doctor_partial_clone.py,tests/test_update_shape_merged_away.py,tests/test_repo_hygiene.pyandtests/test_cli_reference.py, together:141 passed in 146.48s (0:02:26).tests/test_shape_doctor.py:436 passed, 3 skipped, 1 xfailed in 621.17s (0:10:21).155db31f, run 39001429459, git 2.55.0:testsgives11540 passed, 144 skipped, 1 xfailed in 2852.32s (0:47:32).tests-git-bash,packageand SonarCloud pass.Re-measured on main
e3137010(git 2.43.0)RV-f's findings S1, S2 and N1 hold on this main. I ran this PR's new tests against main's own
shape-doctor.py, and four fail for the reasons the issue gives:git -C <clone> fetch --refetch --no-filter origin, which names the first remote, not the one holding C1.--single-branchclone of amainthat lacks C1, with C1 on another branch: run as printed, the NEXT leaves the row the same, "…git cannot list the pinned commit 354efe2695c3 there".whydoes not say the clone lacks the commit. On RV-f's S1 mutant (aNonecount read as nothing lacking), the doctor answered (exit 1) where it should say CANNOT ANSWER (exit 3).N3 holds: this git 2.43 honours
GIT_NO_LAZY_FETCH=1. A read of a missing blob fails with "warning: lazy fetching disabled", and no fetch shows in trace2.What changed and why (
shape-doctor.py)partial_clone_row's NEXT (shape-doctor.py:4614, built byrefetch_commandat:4649) is nowgit -C <shape> fetch --refetch --no-filter <remote> <commit id>….fetch --refetch --no-filter origin <commit>takes the objects missing at that commit from 48 to 0.remotes_holding(:4761, new, called at:4682) lists the promisor remotes whose tracking refs (refs/remotes/<name>/) hold a lacking commit.partial_clone_fetch(:4656) picks the remote from them; round 3 below says how.git for-each-ref --count=1 --contains=<commit>, which reads commits alone. A--filterclone holds every commit a fetch brought, so this fetches nothing (measured: no fetch events in trace2).--containsof it would fetch it.whyspells out the same fetch from each other promisor remote (round 3, N1).why(where_lacked:4709,fetch_said:4722,then_said:4749):objects_lackingis None), it says so (S1): "the pinned commit … is not in this clone at all".--refetchneeds git 2.36 or later (N2).lacking_said,:4633) says "it lacks at least N objects", and "it does not hold the pinned commit … at all" where the commit is absent. A missing tree hides everything under it fromrev-list, so the count is a floor (N1).objects_lacking's docstring says so (:4533), andpartial_clone_row's says why the NEXT now names commits (:4590).GIT_NO_LAZY_FETCH=1process-wide in the doctor would make a missed path fail instead of fetching, and this git honours it, as measured above. But it changes the environment of every row and every child the doctor starts, outside the partial-clone row this PR is scoped to. Left for its own change.fetch --allhas the single-branch weakness too, as the issue says. That row is not the partial-clone row, and is left as it is.Tested (
tests/test_doctor_partial_clone.py)test_the_row_names_the_commit_short_and_the_fetch_that_ends_it(:142), updated: the reason ends "it lacks at least 1 object at the pinned commit …", and the pasted NEXT is[…, "fetch", "--refetch", "--no-filter", "origin", C1].elsewhere(:233) is a standard whosemainis a history of its own, holding C1 only on a branchpinned.single_branch(:251) is a--single-branchblob:noneclone of thatmain, which does not hold C1.two_promisors(:308) is that clone plus the standard one commit on as promisor remotesecond, fetched with the same filter: C1's commit is held onsecond's branches only.test_a_partial_clone_without_the_pinned_commit_cannot_answer(:263, S1): exit 3,lacking == {C1: None}, thewhysays the commit is not in this clone at all, and the clone still does not hold C1 afterwards. "Holds" is asked withrev-list --missing=print,holdsat:215.test_the_next_fetches_a_pinned_commit_only_another_branch_holds(:286, S2): run as printed, the NEXT ends the row (helperrun_the_next_and_ask_again,:279).test_the_next_names_the_promisor_remote_that_holds_the_commit(:319, S2): the NEXT endssecond C1, and run as printed, it ends the row.e3137010, with this PR's tests):4 failed, 20 passed. The four are the tests above, for the reasons listed under "Re-measured".if count}forif count != 0}, on this PR's code):2 failed, 22 passed. The S1 test gets exit 1 for 3, and the single-branch NEXT test fails.With a stub
ghfirst on PATH,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR, in two groups:Results, both on
eb0568ac:tests/test_shape_doctor.py:436 passed, 3 skipped, 1 xfailed in 350.65s (0:05:50).tests/test_doctor_partial_clone.py,tests/test_update_shape_merged_away.py,tests/test_doctor_pin_restore_merges.py,tests/test_doctor_next_is_a_command.py,tests/test_repo_hygiene.pyandtests/test_cli_reference.py, together:210 passed in 161.26s (0:02:41).Pinned copies
None.
shape-doctor.pyis not copied into projects.Docs
None. No doc names the partial-clone row's NEXT; README, AGENTS.md and the handbook say only that the doctor fetches nothing, which still holds.
🤖 Generated with Claude Code
Summary by Sourcery
Make partial-clone doctor guidance converge by fetching each missing commit from an appropriate promisor remote and accurately explaining what remains unavailable.
New Features:
Bug Fixes:
Enhancements:
Tests: