Repository navigation
Description
Goal: every finding and refusal the holder's validate-family.py prints for a member recorded in the wrong case, a gitlink git stops at, or a commit its checkout lacks says what git does and prints a remedy that works in the state it names, and the holder's gate names a member's un-registration before the commit that makes it.
Filed by lane openRepoShape-5, from the checklist candidates of #646's PR (#670: (e), (f), (g)), of #681's PR (#747: the staged un-registration), of rv681's review of it (member-commit-unresolvable), and of #648's PR (#682: the members mode in S1, H1 and U1, member-gitlink-unnamed's sentence, and the cures docstring, with rv648's candidate 2). The family.py add halves of (e) and (g) are in the sibling issue for scripts/family.py.
Lands after: lane 5's #648 (PR #682, fix/648-bootstrap-and-gate-order, head 45c512eb), #681 (PR #747, fix/681-validate-family-remedies, head 031ba75e, stacked on #682) and #704 (PR #745, fix/704-bootstrap-leg-moves, head 7bdfa3cc, stacked on #682). #682 and #747 edit templates/family-root/scripts/validate-family.py, and #682 and #745 the family root's bootstrap.py; the lines below are at main 66b230ed except where a line says 45c512eb or 031ba75e.
Pinned copies. templates/family-root/scripts/validate-family.py and templates/family-root/scripts/bootstrap.py are pinned in every holder (FAMILY_COPIED_VERBATIM, scripts/shape_materialize.py:525-547). Bundle this with the other v1.1.1 edits to them so each holder takes one upstream-changed row per file.
Lines
- (e) After
git mv members/<P> Members/<P>, the move back leaves.gitmodulesnaming the variant._gitlink_in_another_case(validate-family.py:487-548) printsgit rm -r --cached --ignore-unmatch -- <variants>andgit update-index --add --cacheinfo 160000,<pin>,<row path>(:543-548) and no.gitmodulesline.git mvrewrote the section'spathto the variant. Measured by rv648 on bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's head (probe p4b, "the section path moves too"): the move fixes only the gitlink, a second round follows (member-gitmodules-absent'sgit config -fline), then a third (member-uninitialized); lane 3 flagged the same state foraddin RV670's N1. Direction: where HEAD's section gives the variant path, printgit config -f .gitmodules submodule.<name>.path <row path>andgit add .gitmoduleswith the two lines (_section_lines, validate-family.py and validate-pins.py: a staged gitlink removal says to commit it,member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747's shared builder, already prints the lines for a missing section), and name.gitmodulesin the commit. - (f) The move back is a silent no-op where a sparse checkout keeps the variant off disk. The same
git rm -r --cached --ignore-unmatch -- <variant>(:545) has no--sparse, so it exits 0 and removes nothing for a skip-worktree entry, andgit update-index --addthen leaves two gitlinks in the index. RV670's S1 measured that exact line infamily.py addon git 2.43 (--sparseremoves the entry and the gate passes) and wrote "The validator's own line has the same flaw on main. That is for Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459"; the validator's line was not run.git rm --sparseneeds git 2.34 or later (the same limit family.pyadd: #574's spelling remedy gives a generic<new name>aftergit mv members Members,git rm --cachedfor a pulledMembers/README, and agit mv --sparsethat needs git 2.34 or later #685 names forgit mv --sparse). Direction: print--sparsewhere a variant is skip-worktree, asfamily.py adddoes at family.py:add's refusal of a member's own gitlink held only under another spelling of its row's path moves it back to that path in the index alone, so the printed remedy no longer takes the member's gitlink away from its row (#646) #670's head (scripts/family.py:3083-3111), and say "git 2.34 or later" where it is printed. - (g) A file the index holds at or under the row's path, beside the variant's gitlink, gets the move back, which replaces it or stops half done. Read from source, not run:
_check_member_gitlink(:446-486) asksrecorded_gitlink(scripts/repo_shape.py:1239-1262), which skips every index entry that is not mode 160000 (fields[0] != "160000"), then looks for a case variant (:459-462) and prints the move back without asking what else the index holds at or under the path.update-index --addwould replace a file at the row's path without a word and fail on files under it (appears as both a file and as a directory) after its first line has taken the variant out; RV670's N3 reported both foradd's move at79754b99, before family.py:add's refusal of a member's own gitlink held only under another spelling of its row's path moves it back to that path in the index alone, so the printed remedy no longer takes the member's gitlink away from its row (#646) #670's fix round stopped printing it there. Direction: the same questionaddasks at family.py:add's refusal of a member's own gitlink held only under another spelling of its row's path moves it back to that path in the index alone, so the printed remedy no longer takes the member's gitlink away from its row (#646) #670's head (an index entry at or under the row's path keeps the refusal's words and prints no move back), then the file-aside words from theaddsibling issue. - A member's own staged un-registration passes the gate (w681's candidate 2; read from source, not re-run). After
git rm --cached members/IRRSand before the commit, HEAD still records the gitlink, sorecorded_gitlinkanswers from HEAD (its docstring: "HEAD remains the fallback ... one the index never had, and one a conflicted merge holds only at stages 1, 2 and 3"; a staged removal is a fourth, which its reason, "the index is what the NEXT COMMIT will record", does not fit), and_member_gitlinkslists it from HEAD (:1070-1130). The gate says ok;member-gitlink-absentfires only after the commit, so CI is the first to see it. validate-family.py and validate-pins.py: a staged gitlink removal says to commit it,member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747 names a removal staged for a gitlink NO row names (_staged_removals); a row's own is the same state. Direction: decide first whether the gate should name it: HEAD is read on purpose (:997, "a recursive clone of this holder, and CI's"), so it may be a note, not a finding. If so, name it as a staged removal with the two ways out, to commit it or to put the gitlink back withgit reset -- <path>; prove both on a probe holder. -
member-commit-unresolvableprints no remedy (validate-family.py:573-576:Refusal("member-commit-unresolvable", ...)with two arguments;Refusalprints no remedy it was not given,scripts/repo_shape.py:158-174). rv681's candidate 3; rv681's probe P2 gave the two states: a pin that is on the remote but not in.git/modules/(the update fetches it, the gate passes) and a pin no remote holds (the update exits 128not our ref, the gate refusesmember-commit-unresolvable,[2, 2, 0], "correct for a broken pin"). Direction: name what the person does in each: fetch it (git -C <path> fetch) where a remote holds it, andfamily.py bump --to <commit>to a commit a remote does hold where none does; prove each on the P2 holders before the text says it. rv681 also noted that--no-membersexits 0 over a pin no remote holds: that is the mode's contract (:1499-1500printsN member(s) SKIPPED (--no-members)), not a defect, and is written here so nobody changes it. - In S1, H1 and U1 the members mode refuses
member-uninitializedwith a remedy that dies the same way, and no mode names U1. At bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's head, in a plain clone,git submodule update --initdies 128No url found for submodule pathat the first gitlink, in index order, that no section of.gitmodulesgives a url: S1 is a strayMembers/Novabesidemembers/Nova, H1 ismembers/Zedmounted by hand with no row and no section, U1 is Nova's section with no url. bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682 made bootstrap name each and its cure. The validator's members mode still refusesmember-uninitializedfor the first row (:558-567), whose remedy is that samegit submodule update --init --recursive, "ormake bootstrap" (rv648: "the writer's candidate 1, now confirmed for H1 and U1"), and U1 is found by no mode at all (rv648; w648b). Direction: where members mode would refuse and git would stop at another gitlink or a url-less section, print bootstrap's own cure for that state (cures,templates/family-root/scripts/bootstrap.py:162-193at45c512eb), and give U1 a finding of its own. -
member-gitlink-unnamedsays a recursive clone fetches a gitlink no section of.gitmodulesgives, and a recursive clone fetches nothing there.validate-family.py:1292-1294("so a recursive clone of this holder fetches it and nothing here pins, digests or identifies it") and its docstring (:1264-1266, "A recursive clone of the holder fetches it all the same"). w648b measured the stray in a recursive clone (its S2 and X1): nothing is checked out at it, and in a plain clonegit submodule update --initstops at it (S1, H1). Direction: say what git does ("checks out nothing at it; in a plain clonegit submodule update --initstops there"); the text is in a pinned file, so it rides with the lines above. - One docstring sentence in the family bootstrap's
curesis false for family.pyadd, undercore.ignorecase=true, commits the member's gitlink asMembers/<P>when HEAD holds a path under a case variant ofmembers/that the index no longer does (agit rm -r Membersnot yet committed), so.gitmodulesandfamily.yamlsaymembers/<P>, the holder's own validator passes in the working clone, and every fresh clone of the pushed holder leaves the member's directory empty and is refusedmember-gitlink-absent#363's variant (templates/family-root/scripts/bootstrap.py:167-170at45c512eb): "members checked, it refusesmember-uninitializedfor the first row there instead", where git stopped at a gitlink no row names. For family.pyadd, undercore.ignorecase=true, commits the member's gitlink asMembers/<P>when HEAD holds a path under a case variant ofmembers/that the index no longer does (agit rm -r Membersnot yet committed), so.gitmodulesandfamily.yamlsaymembers/<P>, the holder's own validator passes in the working clone, and every fresh clone of the pushed holder leaves the member's directory empty and is refusedmember-gitlink-absent#363's variant the validator refusesmember-gitlink-absentwith the move. The printed lines are right. bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's body says it was left on purpose, so that the head the restacks were modelled on did not move. Direction: reword it ("it refuses, for a variantmember-gitlink-absent, otherwisemember-uninitialized"); docstring only, in a pinned file.
Verify: tests/test_validate_family_unnamed_mounts.py, tests/test_family_head_lost_gitmodules.py, tests/test_family_members_case_variants.py, tests/test_family_bootstrap_gate_order.py (new in #682), tests/test_pinned_copy_text.py and tests/test_repo_hygiene.py pass on Linux. One new case per state, typing each printed line and then the gate: a git mv members/IRRS Members/IRRS holder (gate exit 0, git submodule update --init ok); a sparse checkout that keeps Members/IRRS off disk; a file at the row's path; git rm --cached members/IRRS staged (the gate names it, or the test says it does not and why); a pin no remote holds, with its remedy typed; S1, H1 and U1 in a plain clone through the validator; and a recursive clone of the stray, asserting what the text says. No macOS or Windows run (CI is Linux only, #597); the folding disk is emulated as tests/test_family_members_case_variants.py does.
Overlaps. #682 and #747 edit validate-family.py; #682 and #745 (fix/704-bootstrap-leg-moves) edit the family root's bootstrap.py; this lands after all three. fix/695-family-docs (#738) and l3-563-docs (#630) edit README and the handbook, which no line here changes. fix/646-add-member-variant-remedy (#670) edits scripts/family.py only. No other open branch touches either file.
Parent: #459.
Milestone: v1.1.1. Each state needs a hand git mv, a sparse checkout, a pin no remote holds or a hand-edited .gitmodules. None is a regression: #639's remedy is new in v1.1.0 and is right in the states it was built for, and these are the states it was not. None blocks v1.1.0.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 17, 2026
CLAIMED — lane openRepoShape-5, session 50cede29-ab82-4ca3-9f67-d98e1a58c8d2@Eagle, 2026-10-17T10:27:43Z, for #762
Logged in
lanes/log/openRepoShape-5.mdat1c7815f3a5f0ce9267665f88c9174519dc709acf.The three reads (lane-collision-protocol Rule 1):
tests/test_validate_family_typed_states.py: each `commit_ahead` commi…
validate-family.py: `member-uninitialized` reads the `.gitmodules` gi…
validate-family.py: the wrong-case move back mends `.gitmodules`, add…
Merge origin/main (`b9b70692`) into fix/768-gitmodules-values: #820 (#…
tests/test_family_gitmodules_values.py: a staged `git rm --cached` be…
tests: three more hand-worked `GIT_CONFIG_COUNT` appends go through `…
Merge origin/main (b9b7069) into fix/763-holder-docs-agree