Repository navigation
shape-doctor.py: the legs and members NEXT names every detached commit it would move, and sends a mount with no .git of its own, an unborn HEAD, a mount git finds no url for, and a leg with its own lockstep fact to a line that cures it or to none (#413, #414) - #559
Merged
Merged
Conversation
…ommit it would leave in a reflog alone, so the repository git kept for an emptied mount is read through `--git-dir` and kept with a line that names it, the holder's own bootstrap carries the same keep sentence, and a members line says `--recursive` checks out each member's own legs too (#413) `submodule_update_next` named a keep line only for a POPULATED mount detached off its pin (`off_pin`). The line moves three more HEADs that can hold the only copy of a commit, and after the paste each such commit was held by a reflog entry alone, with nothing said. (a) A mount emptied by `git submodule deinit -f` keeps its repository in `.git/modules/<name>`, and `submodule update` links the mount to it again and checks the gitlink out over the commit. `emptied_reach` finds that repository by the submodule's NAME (`gitmodules_entry`, which reads `.gitmodules` where git reads it: the working tree, else the index, else HEAD) and `git rev-parse --git-path modules/<name>`, and reads its HEAD through `--git-dir` (`kept_head`), never in the mount, where git walks up and answers about the root. Detached at a commit that is not the gitlink, the rule `off_pin` already used (`left_in_reflog`), the commit is named with `git --git-dir=<repository> branch NAME <commit>`: in the empty mount `git -C <mount> branch NAME` would branch the ROOT. With the mount directory itself gone, git refuses every command in that repository (`cannot chdir`, exit 128, measured), because its `core.worktree` names the mount, so the read and that keep line carry a `--work-tree`. (b) The holder's own `scripts/bootstrap.py`, the `members` NEXT where it can run, runs the same update over every member (`fetch_members`) and said only that it leaves each member detached at its pin. Its `why` now carries the same keep sentence, built by the same function (`checkout_notes`, factored out of `submodule_update_next`), for every member it would move off a detached commit, not only those the row sends to a checkout, because its update names no path. (c) `--recursive` checks out each member's own legs at the commits the MEMBER records. The row does not walk into them: a `members` line says so in general words, with `git -C LEG branch NAME` to run in that leg first, wherever a member it reaches has a repository for those legs to be in. `--recursive` stays, for parity with the holder's bootstrap. `detached` takes a `git_dir` for a kept repository, and answers None, not "detached", when `symbolic-ref` fails for any reason but a detached HEAD. Tests at the end of tests/test_doctor_legs_members_next.py, in the shape of `test_the_keep_line_in_the_explanation_keeps_a_detached_commit`: for each of (a), (b) and (c) the commit is named before the paste (by its sha in (a), by its mount's keep line in (b), in general words in (c), which does not walk), the keep line run first leaves it on a branch, and without that line no ref holds it afterwards. A member on a branch still gets no keep line of its own, and the existing test that asserted no `reflog` at all in its `why` asserts that instead. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t with no `.git` of its own and send it to the git line when git kept its repository, name an unborn HEAD and a mount `.gitmodules` gives no url for with no NEXT instead of a gate that says `pins ok` or a line that exits 128, and say to run the doctor again for a leg the line checks out whose own pin is wrong (#414) #414's four states, found by the #276 review, and the `members` row, which reaches each of them: (1) A populated mount with no `.git` of its own. `head_of` asked git in it, git walked up, and the row quoted the ROOT's HEAD as the leg's. No HEAD is read there now (`member_state` likewise), and the mount is routed on what `submodule update` routes on, whether git kept its repository (`checkout_reach`): kept, the update links the two again and the row is `ok`, so it is the git line's; gone, the update tries a clone into a directory that is not empty and exits 1, so it stays the lockstep gate's. The `why` says which. (2) An unborn HEAD (`unborn_branch`) is no lockstep fact: the gate said `pins ok` and the row stayed red, and the update exits 128. Nothing cures it without choosing a commit for the person, so the NEXT is None and the `why` names the state. (3) A mount `.gitmodules` gives no url for. The git line's `--init` exits 128 on `No url found`, for an empty mount and a populated one alike, so `gitmodules_entry` is asked before the line is chosen. The gate, measured, names no `.gitmodules` entry: `validate-pins.py` refuses `pin-submodule-uninitialized` and `validate-family.py` `member-uninitialized`, each handing back the same git line. So the NEXT is None and the `why` says a person restores the entry from the repository's history; no command is printed, so none discards an uncommitted `.gitmodules`. A `.gitmodules` deleted from the working tree alone is read from the index, as git reads it, and the line stands. In a holder, a member with no url takes the holder's own bootstrap off the line: its update names no path, git stops it at a section with a path and no url before it fetches any member (exit 128), and the bootstrap still exits 0. The git line over the other members is the NEXT. (4) `rerun` is asked per leg: a leg the line checks out whose own pin is not 40 hex, or not its gitlink, gets "run the doctor again" too, and so does the holder's bootstrap line. The precedence stays: a checkout first, then the lockstep gate, then no line. Both rows route through `checkout_reach`, one definition. And #533's two stale docstrings, moved here from P6: `submodule_update_next`, `check_legs` and tests/test_doctor_legs_members_next.py said bootstrap "never moves an existing branch whose tip is not the pin". Since #475 and #490 it fast-forwards one behind the pin and moves back one ahead of it with no commit of its own; any other branch holding a commit the pin does not contain stays where it is. They say so now. Tests at the end of tests/test_doctor_legs_members_next.py, for both rows: each NEXT, pasted, turns the row `ok`, or the NEXT is None and the `why` or the reason names the state. (1) never quotes the root's HEAD, (3) never prints a line that exits 128, and one test pins what the git line and the gate do there, and (4) says to run the doctor again. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUpdates the doctor’s legs and members NEXT routing to avoid unsafe or ineffective commands, accurately account for every detached commit a checkout or bootstrap may move, and provide shared, tested explanations for retained repositories, recursive updates, unborn HEADs, missing submodule URLs, and follow-up lockstep facts. Sequence diagram for safe submodule checkout routingsequenceDiagram
participant Doctor
participant Gitmodules
participant KeptRepo
participant Git
Doctor->>Gitmodules: gitmodules_entry(root, rel)
alt URL is missing
Gitmodules-->>Doctor: no-url
Doctor-->>Doctor: NEXT None
else URL exists
Doctor->>KeptRepo: kept_gitdir(root, name)
alt Repository was kept
Doctor->>KeptRepo: kept_head(root, git_dir)
KeptRepo-->>Doctor: detached state and commit
else No kept repository
KeptRepo-->>Doctor: None
end
Doctor->>Doctor: checkout_reach(...)
alt checkout or relink
Doctor->>Git: submodule_update_next(...)
Git-->>Doctor: checkout command and keep notes
else gone
Doctor-->>Doctor: lockstep_next(...)
end
end
Flow diagram for legs and members NEXT routingflowchart TD
A[Inspect leg or member state] --> B{Checkout can cure it?}
B -->|No| C{Unborn HEAD or missing URL?}
C -->|Yes| D[NEXT: None; explain manual repair]
C -->|No| E[NEXT: lockstep gate]
B -->|Yes| F[checkout_reach]
F --> G{Route}
G -->|checkout or relink| H[git submodule update --init --recursive --checkout]
G -->|gone| E
G -->|no-url| D
H --> I[Add keep and rerun guidance]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…rl` keeps the git line while the root's own config still holds one, a leg's own submodules are named in general words as a member's legs are, the lockstep gate's `why` carries the states no line cures, and tests pin `detached`'s None and every member the holder's bootstrap moves (#414, #413) Fix round 1 for PR #559, after the review R559 at 83b3865. The url check asked `.gitmodules` alone, and that was a regression from main. `git submodule init` copies a mount's url into the root's config, and `--init` reads it there first. So an INITIALIZED mount whose section kept its `path` and lost its `url` key still updates: measured, the git line exits 0 and cures the row, whether the leg is populated past its pin or emptied by `rm`, and the same holds for a member under the holder's own bootstrap. The row gave NEXT None and a `why` saying the line would exit 128. A mount is now `no-url` only when no section gives its path, or the section has no url and `git config --get submodule.<name>.url` in the root finds none (`configured_url`). `deinit` drops the configured url and a missing section leaves git no name to look one up by, so both still get NEXT None. The reason and the `why` say "in .gitmodules, nor in the config of this repository". The `legs` line's `--recursive` checks out the submodules a leg carries of its own, and a commit made on one's detached HEAD was left in its reflog unannounced (R559's p3). A leg the line names that carries a `.gitmodules`, or whose kept repository keeps `modules/`, now gets the general sentence a member's legs get (#413 c), with the placeholder keep line `git -C LEG/SUB branch NAME`. The row walks into none of them. `checkout_reach`'s third answer is now that "carries submodules" fact, for both rows. Where the lockstep gate is the NEXT, an unborn HEAD or a mount with no url beside it is said in the gate's `why` with the same sentence it gets when no line is printed, and that sentence ends by saying to run the doctor again. The gate itself cures nothing, so no separate re-run sentence is added. `checkout_notes` and `submodule_update_next` no longer say that the sentence carries none of `;`, `'`, a backtick or a parenthesis. That rule bound a ` # ` tail pasted with the line, and since #277 the sentence is the row's `why`, on a line of its own. New tests at the end of tests/test_doctor_legs_members_next.py: - the url the root's config keeps, for a leg past its pin and one emptied by `rm`, and for a member: the line is offered, and pasted it cures the row; - a leg carrying submodules gets the sentence and a leg without one does not, and with a real nested submodule the placeholder keep line, filled in and run first, keeps the commit, while without it no ref holds it; - a second member the row hands to the gate but the holder's bootstrap moves is named by its keep line, and pasting proves the move; - `detached` answers None where `symbolic-ref` fails, and a member whose gitdir is gone is not called detached in `--json`; - an unborn leg beside a lockstep one is said in the gate's `why`. `add_member_row` is factored out of the url-less member's helper. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
legs and members NEXT names every detached commit it would move, and sends a mount with no .git of its own, an unborn HEAD, a mount .gitmodules gives no url for, and a leg with its own lockstep fact to a line that cures it or to none (#413, #414)legs and members NEXT names every detached commit it would move, and sends a mount with no .git of its own, an unborn HEAD, a mount git finds no url for, and a leg with its own lockstep fact to a line that cures it or to none (#413, #414)
…r's own bootstrap whenever any member with a gitlink is one git finds no url for, routed or not, a commit in a submodule whose directory is gone is kept by a line spelled through its repository, a leg whose working tree dropped its submodule is asked for its repository's `modules/`, and the `.gitmodules` listing is read by a pure helper (#413, #414) Fix round 2 on #559, from R559v's verification of aa63ff6. S2 (#414). The holder's own bootstrap updates with no path named (`fetch_members`), so git stops that whole run at the first gitlinked member it finds no url for, whatever the row says of that member. R559v measured it: beside an emptied IRRS, a second member at its pin, which the row passes over, or recorded off it, which the row hands to the family gate, with no `.gitmodules` section or a path-only one and no url in the holder's config, left the bootstrap as the NEXT. Pasted, it printed `FETCH INCOMPLETE` and `No url found` and the row stayed red, while the git line over IRRS alone exits 0 and fills it. The url question, the section (`gitmodules_entry`) and then the root's own config (`configured_url`), is now one helper, `submodule_url`. `checkout_reach` asks it, and `url_less_members` asks it of every member with a gitlink before the bootstrap is chosen. Any such member takes the plain git line, and each one is named in the `why` by its `no_url_sentence`, so the reader knows which entry to restore. N6 (#413). Where the nested trigger is the `modules/` of a repository git kept for an EMPTY mount, the submodules' directories are gone with it, and `git -C LEG/SUB branch NAME` exits 128 (`No such file or directory`). `checkout_reach` now says which keep line a mount's own submodules need (`nested_kind`: `tree` where the working tree carries a `.gitmodules`, `repo` where only a repository keeps theirs), and `nested_sentence` spells the second: `git --git-dir=REPO/modules/SUB --work-tree=ROOT branch NAME HEAD`. The work tree is the root because the submodule repository's own `core.worktree` names the missing directory (`cannot chdir`, exit 128, without it). Filled in and run first, it exits 0 and the commit is on that branch after the paste, for a leg emptied by `deinit` and for a member's own leg after the member is. N7 (#413). A leg with its own `.git`, past its pin at a commit that dropped its submodule, carries no `.gitmodules`, yet the gitlink's commit mounts the submodule and `--recursive` links its kept repository again and checks it out. `keeps_modules` asks the mount's own repository (`rev-parse --absolute-git-dir`) for its `modules/`, and the same spelled line keeps the commit. It is under ten lines of code, so it is done here rather than put on #460's checklist. S3776. `gitmodules_entry`'s record parsing is now a pure helper, `section_url(listing, rel)`, unit-tested on git's own `-z` listing of a `.gitmodules` whose section name holds a space and a dot: the last url a section gives wins, the last section giving a path wins, and an empty url is none. S9073. The three composite test asserts are split into single asserts that keep their message, so a failure says which half failed. N8. The members twin of the test that the gate's `why` carries what no line cures: an unborn HEAD in IRRS beside a member recorded off its pin, the family gate as the NEXT, and the unborn sentence, which says to run the doctor again, beside it. S1192 (`".gitmodules"` repeated) is accepted, as the coordinator ruled. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
…ean merge for ruling 27 This is a clean merge of main a2243f1 (#557, on #560) with no hand resolution: `git merge` auto-merged shape-doctor.py and tests/test_doctor_legs_members_next.py and reported no conflict, and its tree, e0e7f1e, equals what `git merge-tree --write-tree 66a9353 origin/main` prints (T). T is also the tree the targeted set passed on, committed in a scratch worktree before this merge existed: 997 passed, 3 skipped (the three PowerShell tests, Windows only). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
brettheap
marked this pull request as ready for review
October 15, 2026 14:08
|
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 2 days and 11 hours by commenting |
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…y-hand test and #581 among them) into #569, so the PR's CI runs on current main Merges origin/main fb786c1, read from the shared store without a fetch. Main moved from 7d9bec9 (#569's last merge) by #556, #557, #558, #559, #560, #562, #567, #568, #579, #580 and #581. #579 (#550) landed tests/test_setup_by_hand_commands.py, which this PR's `--no-next-steps` makes red; the next commit answers it. The merge itself is clean, and its tree is the pm569 prediction's #581 step (53b6d0f7)'s, so the resolution prepared against the predicted main carries over. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
… fix/540-pin-source-unread This merges origin/main 2bf8fd4 as the shared store held it at 2026-10-15T16:50Z, thirteen landings past the 7d9bec9 this branch last merged: #555 (shape-doctor's manifest kinds), #565 (scaffold's `scaffold-remote-exists`), #581 (adopt's `check` of a kept pin path and an unwritable work directory), #568, #559, #557, #560, #580, #556, #558, #567, #579 and #562. It is done so that the PR's CI merge ref and this round's rewording run on current main. The merge was clean, with no conflict hunk. `adopt-project.py`, `scaffold-project.py`, `scripts/shape_materialize.py`, `docs/cli.md` and `tests/test_adopt_root_symlinks.py` took main's changes beside this PR's by the textual merge, and `scripts/render-cli-reference.py --check` passes on the result. Main: 2bf8fd4. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
12 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-4 (openRepoShape-4)
Claim: #413 (comment)
Claim: #414 (comment)
What changed and why
Since #276 (PR #354) the doctor's
legsandmembersrows hand overgit -C <root> submodule update --init --recursive --checkout -- <paths>where a checkout cures the row, and the lockstep gate where none does. Since #277 (PR #402) the row'swhysays what the line does. This PR fixes the two follow-ups this lane's review of #354 filed, in the same functions. Both issues' repro scripts split the NEXT on#. Since #277 that split finds an empty sentence on main, and the sentence is the row'swhy("why"in--json).#413: every detached commit the line would move is named
The keep sentence ("A commit made in a leg on no branch is left only in its reflog by this line. To keep one, run first git -C branch NAME") was printed only for a populated mount detached off its pin. The line moves more HEADs than that, each able to hold the only copy of a commit:
git submodule deinit -fkeeps.git/modules/<name>. The update links the mount to it again and checks the gitlink out over the commit. The row now finds that repository by the submodule's name (.gitmodules, read where git reads it) andgit rev-parse --git-path modules/<name>. It reads that repository's HEAD through--git-dir, never in the empty mount, where git walks up to the root. When the HEAD is detached at a commit that is not the gitlink (left_in_reflog, the rule the populated mounts use too), the row names the commit withgit --git-dir=<repository> branch NAME <commit>. Run in the empty mount,git -C <mount> branch NAMEwould branch the ROOT. If the mount directory is gone too, git refuses every command in that repository (cannot chdir, exit 128, measured), so that keep line then carries a--work-tree.scripts/bootstrap.py. Its update names no path, so it moves every member, including one the row hands to the family gate. Itswhynow carries the same keep sentence, built by the same function (checkout_notes, factored out ofsubmodule_update_next), for every member it would move off a detached commit.--recursive. It checks out the submodules a mount carries of its own, at the commits that mount records: each member's legs, and anything a leg mounts. The row does not walk into them. A line that names a mount carrying some (a.gitmodulesin it, or a kept repository that keeps theirmodules/) says so in general words, with a placeholder keep line to run first:git -C LEG branch NAMEfor a member's leg,git -C LEG/SUB branch NAMEfor a leg's own submodule.--recursivestays, for parity with the holder's bootstrap (fetch_members).#414: four states, each sent to a line that cures it or to none
.gitof its ownwhysays it links the two again; pasted, the row isok. If not, the NEXT is the gate, whosewhysays git kept none: the update tries a clone into a directory that is not empty and exits 1 (measured).pins okwhile the row stays redwhysays nothing here chooses a commit..gitmodulessection gives the path, or the section has no url and the root's own config has none eitherNo url found)whysays a person restores the entry from the repository's history. The gates were measured:pin-submodule-uninitialized, andmember-uninitializedfor a member, name no.gitmodulesentry and hand back the same git line. No command is printed, so none can discard an uncommitted.gitmodules.For (3), the url is asked for every mount the line would name, populated ones too:
--initexits 128 on a populated one as well (measured)..gitmodulesis read where git reads it, so a copy deleted from the working tree alone is read from the index, and the line stands (pasted, it exits 0). A section that keeps itspathbut lost itsurlkey is not url-less while the root's own config still holds the urlsubmodule initcopied there:--initreads that first, and the line exits 0 and cures the row (measured, populated past its pin and emptied byrm;deinitremoves the configured url).The
membersrow takes the same rules through one function,checkout_reach, and a member reaches all four states. A holder adds one case. Git stops the holder's bootstrap at a member it finds no url for: the bootstrap's update names no path, git exits 128 before any member is fetched, and the bootstrap still exits 0 with the row as red as before. So the NEXT is then the git line over the members a checkout cures, and itswhysays why it is not the bootstrap.Where the lockstep gate is the NEXT, an unborn HEAD or a mount with no url beside it is named in the gate's
why, in the same sentence it gets when no line is printed. That sentence ends by saying to run the doctor again.Review round 1 (R559, at 83b3865)
.gitmodulesalone, a regression from main for an initialized mount whose section lost only itsurlkey. It now also asksgit config --get submodule.<name>.urlin the root (configured_url), so such a mount, populated or emptied byrm, keeps the curing git line, as on main. The holder's bootstrap does the same for a member.--recursivesentence, as a member's legs do.detached()'s None, and every member the holder's bootstrap moves.#tail rule to thewhy.whynow carries the states no line cures.Tests
The new tests go at the end of
tests/test_doctor_legs_members_next.py: 13 for #413, 21 for #414, and 15 from review round 1. Each runs for thelegsrow and themembersrow where the state applies. One existing test there changes. It asserted noreflogin a branched member'swhy, which now carries the nested sentence, so it asserts instead that the member gets no keep line of its own.legsandmembersNEXT after #354 moves a detached HEAD that holds the only copy of a commit, with no reflog note, in three states: an emptied leg mount whose kept gitdir holds the commit, a holder whose ownscripts/bootstrap.pyis the NEXT (any commit made in the pinnedmembers/<P>), and a commit in a member's own leg that--recursivemoves, so after the paste the commit is held by a reflog entry alone #413 (a), (b) and (c): each has the shape oftest_the_keep_line_in_the_explanation_keeps_a_detached_commit.legsNEXT after #354 does not fit four edge states: a populated mount with no.gitbut its gitdir kept is sent tovalidate-pins.pyunder a sentence saying no update cures it, though the git line does; an unborn HEAD gets the gate, which sayspins okwhile the row stays red; an empty mount with no.gitmodulessection gets a git line that exits 128; and an empty mount whose pin is not 40 hex gets no "run the doctor again" #414 (1) to (4): each NEXT, pasted, turns the rowok, or the NEXT is None and thewhyor the reason names the state.No url found) and the gate (pin-submodule-uninitialized, no.gitmodulesin its output) do there.rm, and a member.detached()answers None wheresymbolic-reffails (a member whose gitdir is gone is not called detached in--json), and an unborn leg beside a lockstep one is named in the gate'swhy.WINDOWS_SKIP, as the file's others are: 7 for shape-doctor.py'slegsandmembersNEXT after #354 moves a detached HEAD that holds the only copy of a commit, with no reflog note, in three states: an emptied leg mount whose kept gitdir holds the commit, a holder whose ownscripts/bootstrap.pyis the NEXT (any commit made in the pinnedmembers/<P>), and a commit in a member's own leg that--recursivemoves, so after the paste the commit is held by a reflog entry alone #413, 6 for shape-doctor.py'slegsNEXT after #354 does not fit four edge states: a populated mount with no.gitbut its gitdir kept is sent tovalidate-pins.pyunder a sentence saying no update cures it, though the git line does; an unborn HEAD gets the gate, which sayspins okwhile the row stays red; an empty mount with no.gitmodulessection gets a git line that exits 128; and an empty mount whose pin is not 40 hex gets no "run the doctor again" #414 and 7 from round 1. Every text-level assertion runs on all three OSes.Run on Linux (py-bench) at this head's bytes (
shape-doctor.pysha256d85e92ac…, the test file7b765cae…), with git-filter-repo on PATH:tests/test_doctor_legs_members_next.py tests/test_doctor_unfinished_merge.py tests/test_doctor_next_is_a_command.py tests/test_shape_doctor.py tests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_agents_md_codes.py: 654 passed, 3 skipped (10 min 38 s).tests/test_family.py::test_the_doctor_reports_a_project_pinned_twiceandtests/test_doctor_validator_rows.py's twotest_a_colon_standard_withholds_the_{legs,members}_rows_lockstep_line*. 3 passed.manifest kindsprints one command for its NEXT, thegit rm,git cleanorgit statusits files need as git has them, where it printed prose joined by;whose backticks ranfamily.pyin zsh, and counts a manifestvalidatedonly at a path its gate reads, so acontracts/<x>-pin.yamlno leg or product names is a FINDING (#548, #439) #555, tests: every test that clones a just-pushed local remote by its plain path now clones it withclone_just_pushed, so the macOSfailed to copy filerace of #440 cannot reach it;load_without_bytecodekeeps the doctor's and the Windows-path tests' loads of the family root's scripts from writing.pycinto the templates; the not-a-list doctor cases hold INVALID, exit 1; andGOOD_KEYSholds th #554 and shape-doctor.py: a validator's verdict is a printedFINDINGorREFUSEDand any other non-zero exit is a crash, so a gate killed by a signal, ended byos._exit(1), holding conflict markers or failing argparse no longer "reports a finding", a crash of the standard's own naming script is CANNOT ANSWER instead ofINVALID (naming), and a pass that printedWARNINGornotelines is anotequoting them while a red row quotes its finding and never a warning (#410, #448) #557,tests/test_doctor_legs_members_next.py: 111 passed.No
tests/test_family.pytest reads the doctor'smembersrow. Its only doctor test reads thefamilyrow, and runs above.Mutants, each run against its own tests, then reverted (sha256 checked):
kept_headanswers nothing)-k 413a-k 413brerunfrom other legs only-k 414_4legscases of (4)detached()reads any failure as detached, as main did-k detacheddetachedtest and the member--jsontestoff_pinonly-k 413b.gitmodulesalone-k 414_3legsline-k submodulewhywithout the states no line cures-k gate_is_the_linemacOS and Windows were not run here; this PR's CI is their first run.
Shared files
shape-doctor.pyis also changed by other open PRs, on other lines. Each is named by the functions its hunks sit in, and the functions it adds:FINDINGorREFUSEDand any other non-zero exit is a crash, so a gate killed by a signal, ended byos._exit(1), holding conflict markers or failing argparse no longer "reports a finding", a crash of the standard's own naming script is CANNOT ANSWER instead ofINVALID (naming), and a pass that printedWARNINGornotelines is anotequoting them while a red row quotes its finding and never a warning (#410, #448) #557 (P2,fix/doctor-validator-verdicts, shape-doctor.py's twonamingrows read only the naming script's exit code, so a crash of the STANDARD's ownvalidate-repository-naming.pyis a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 getsnaming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410/shape-doctor.py'smanifestrow saysok ... passeswhen the project's ownvalidate-manifest.pyexits 0 after printingWARNING naming-referent-chain: ... not reachable here, and the warning appears nowhere in the text or--jsonreport, so a referent chain nothing on this disk could check reads as checked underCOMPLIANT: the class of theskip#357 makes anote, in the form #357 still drops #448):run_validator,validator_row,report,offline,check_namingandcheck_not_a_root_naming; it addssignal_name,reported,skipped,passed_butandnaming_crashed_row;manifest kindsprints one command for its NEXT, thegit rm,git cleanorgit statusits files need as git has them, where it printed prose joined by;whose backticks ranfamily.pyin zsh, and counts a manifestvalidatedonly at a path its gate reads, so acontracts/<x>-pin.yamlno leg or product names is a FINDING (#548, #439) #555 (l3-548-manifest-kinds): the module docstring,ManifestKind,check_manifest_kinds,registered_elsewhere,linked_record_row,git_states,family_beside_project,placement_policy,refusal_lineandrun_checks; it addsown_pins,pins_read_here,read_by_a_gate,remedy_as_why,listed,register_way,read_nowhere,read_here,removal_nextandtaking_out;--org <your-org>where itsoriginnames no organisationplancan read, so pasting it is no longer refusedadopt-no-org(#545) #553 (l3-545-doctor-way-in): the module docstring,CHECKS,check_the_way_in,python_command,repo_local_origin_nameandscaffold_command; it addsorigin_org,way_in_projectandcheck_the_other_way_in;machinerow of a machine that is not ready names the preflight checks that failed, in the[!!]lines the preflight printed, so its reason no longer ends onthis machine is not ready yet (see above).with nothing above it (#549) #552 (l3-549-doctor-machine-row):check_what_is_hereandcheck_machine; it addspreflight_failures,is_preflight_verdictandfirst_sentence.This PR touches
submodule_update_next,check_legs,detached,member_state,member_problemandmembers_verdict, and adds its helpers betweenlockstep_nextandsubmodule_update_next. None of the four adds a function under a name this PR adds.tests/test_doctor_legs_members_next.pyis also changed by lane 3's #554 (l3-544-test-hygiene): its import line andtest_a_plain_clones_empty_mounts_are_cured_by_the_pasted_line. This PR changes the module docstring and one other test,test_the_members_reflog_note_is_printed_for_a_detached_member_only, and appends its tests at the end.git merge-tree --write-treeof this head with origin/main and with each of them exits 0 with a tree, so none conflicts. The heads were main b8b7062, #557 430c13f, #554 4619757, #555 abbce43, #553 e64f47e and #552 9362501. Main's commit since this branch's base, 5ed3e5d (#518), changes none of this PR's files.The two stale #533 docstrings in
shape-doctor.py(submodule_update_next,check_legs), and the test file's own, are corrected here: since #475 and #490 bootstrap does move a branch whose tip is not the pin, where that loses nothing.v1.1.0: lands only after the v1.0.0 tag.
Closes #413
Closes #414
🤖 Generated with Claude Code
Summary by Sourcery
Make shape-doctor accurately route legs and members repairs while warning about every detached commit and nested checkout that a proposed update may move.
Bug Fixes:
Enhancements:
Tests: