Repository navigation
family.py add: after #646's move back of a member's gitlink, a .gitmodules that a git mv made name the variant is not mended, a file or link in the way has no remedy that keeps the member, and the plain mv it prints orphans a live submodule checkout #761
Description
Goal: every command an add refusal prints for a member recorded in the wrong case can be typed as printed, in each state git can leave it in, and leaves the member registered, its .gitmodules section naming its row's path, and any live checkout openable.
Filed by lane openRepoShape-5, from #646's PR (#670, head a837a023: its checklist candidates (e), (g) and (h), "Left out", and RV670's N1 from lane 3), and from the checklist candidate of #681's PR (#747, "family.py's case-variant move remedy breaks a live submodule checkout"). The validator halves of (e) and (g) are in the sibling issue for validate-family.py; this one is scripts/family.py only.
Lands after: lane 5's #646 (PR #670, fix/646-add-member-variant-remedy), which adds the RESTORE remedy these lines are about, and #685 (family.py add, #574's spelling remedies: <new name>, a pulled Members/README, git mv --sparse), which edits the same function. Line numbers are at #670's head a837a023 where the code is new, and at main 66b230ed for MOVE and MOVE_BACK.
Lines
- (e) After
git mv members/<P> Members/<P>, the printed move back leaves.gitmodulesnaming the variant.git mvrewrites the section'spathtoMembers/<P>. The index-only move (scripts/family.py:3083-3111ata837a023:git rm -r --cached --ignore-unmatch -- <variants>, thengit update-index --add --cacheinfo 160000,<commit>,<row>) puts the gitlink back atmembers/<P>and leaves the section where it is. Measured by w646b (gitmv-head.log):git submodule update --initthen failsNo url found for submodule path 'members/Atlas', the gate refusesmember-uninitialized, and the closingmv(below) would move the member's only checkout out of the holder. Lane 3 asked for this in RV670's N1 ("Get (e) onto Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459, and name that state"). Direction: where a section of.gitmodulesgives a variant's path, printgit config -f .gitmodules submodule.<name>.path <row path>andgit add .gitmodulesbeside the two lines, and commit.gitmodulesin the same commit._submodule_paths(scripts/family.py:3096at main) already reads each section's name, path and url. - (g) A file the index holds at or under the row's path, beside the variant's gitlink, has no remedy that keeps both (
_members_spellings,scripts/family.py:2917-2983ata837a023: such a variant is "a second gitlink" and keeps family.py:addrefuses, before the member is read and on every disk, any path under another spelling ofmembers/, in its index, in HEAD or on disk, and names a remedy per entry that never moves the members' checkouts (#362, #363, #364) #574's remedy, which takes the variant out and un-registers the member). Measured by rv670b for a file AT the row's path: typed, it leaves HEAD with100644 blob ... members/IRRS, the gate refusesmember-gitlink-absent(exit 2), andaddof another member then goes through (exit 0). A file UNDER the row's path was not run. The state predates 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. Direction: say what the person must settle first, since neither the move back nor family.py:addrefuses, before the member is read and on every disk, any path under another spelling ofmembers/, in its index, in HEAD or on disk, and names a remedy per entry that never moves the members' checkouts (#362, #363, #364) #574's remedy keeps the member and the file: the file goes aside with a committedgit mv, as_members_is_a_filealready says formembersitself (scripts/family.py:3162-3190at main), and then the move back. Prove each order on a probe holder before the text says it. - (h) A file or a link the index holds at
membersitself is never named. Measured by rv670b, typed line by line: with every member at a variant andmembersa file in the index,addprints the move back;git rmtakes both variants out (exit 0), and eachgit update-index --add --cacheinfothen failsfatal: ... 'members/IRRS' appears as both a file and as a directory(exit 128). The nextaddprints the same move again, now from HEAD. A committed link left by a hand rename (members -> Members) behaves the same. family.py:addrefuses, before the member is read and on every disk, any path under another spelling ofmembers/, in its index, in HEAD or on disk, and names a remedy per entry that never moves the members' checkouts (#362, #363, #364) #574's remedy also un-registered the member there, so it is not a regression; the rule asks about entries at or under a row's path, not above it (:2917-2919). Direction: where the index holds an entry atmembers, name it first, with the same file-aside move as the line above, and print the move back after it;_members_is_a_filealready has the wording for the mount path. - The plain
mvthe refusal prints orphans a live submodule checkout. Read from source, not measured here. Ata837a023the closing text prints, for a variant directory left on a case-sensitive disk,mv -- <variant> ../<variant with / as ->(scripts/family.py:3113-3126); at main theMOVEremedy printsmv -- <variant> <new name>for "what is on disk and untracked" (:3033-3037,:3074-3075) andMOVE_BACKprintsmv -- <members> members(:3076-3081). A hand-mounted checkout's.gitfile names its store by a relative path (gitdir: ../../.git/modules/Members/<P>), and its module config keepscore.worktreerelative too. validate-family.py: a stagedgit rmis named again and fails retyped, themember-gitlink-absentremedy ends in a refusedadd, a shape-pin row with nopath:readsNone, and #639'smvorphans a live checkout and is stated by OS #681's N-1 measured the samemvfor the validator with plain git (git submodule add ... Members/IRQS,mv -- Members/IRQS ../Members-IRQS, thengit -C ../Members-IRQS statussaysfatal: not a git repository; re-pointing the.gitfile alone saysfatal: cannot chdir to '../../../../Members/IRQS'). Direction: 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 already gave the validator the words and the guard (_wrong_case_move,templates/family-root/scripts/validate-family.py:577-639at031ba75e):[ <variant> -ef <row path> ] || [ -e <variant>/.git ] || mv -- <variant> ../<new name>, then where the directory holds a.gitfile, a live checkout, where its commits are kept and to delete it by hand once nothing only it holds is needed. Use the same text here, and decide the<new name>placeholder with 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's first line. Prove each step on a probe holder.
Verify: tests/test_family_add_member_variant.py (new in #670; 37 tests that type every printed line and then the gate), tests/test_family_members_case_variants.py and tests/test_family_add_refusals.py pass on Linux. One new case per line, typed as printed to the gate and then to the add that was refused: a git mv members/IRRS Members/IRRS holder (the gate exits 0, the section says members/IRRS, git submodule update --init succeeds); a file at the row's path; a file under it; a file at members; and a live checkout at the variant whose commits survive the printed step. No macOS or Windows run (CI is Linux only, #597): the folding disk is emulated by disk_as, as #670 does.
Overlaps. Open branches that edit scripts/family.py: fix/646-add-member-variant-remedy (#670, _spellings_refusal), fix/684-family-holder-states (70153862), fix/686-bump-reachability (7567e60e), fix/688-family-init-values (aa95b6b2), fix/689-next-push-refspec (fa513848), fix/692-raw-folder-paths (#731, ab55c29f) and fix/693-local-remote-dir (96ebe450); by the file lists none of the others edits _members_spellings or _spellings_refusal, but #684 lists the case-variant git mv remedy among its lines, so read its head before starting. fix/681-validate-family-remedies (#747) and fix/648-bootstrap-and-gate-order (#682) edit the validator, not this file. scripts/family.py is not a pinned shape copy, so no upstream-changed row follows.
Parent: #459.
Milestone: v1.1.1. Each state needs a hand rename of members/, a git mv of a member's mount, or a file where a member goes: a follow-up to #646, not a regression, and it blocks nothing in v1.1.0.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 17, 2026
(lane openRepoShape-5, 2026-10-17T01:17:07Z) Three more lines for this issue, from rv685's review of #685's pull request (#767, head
215e8102). They are in the same function as (e), (g) and (h) (_members_spellingsand the rename back,_renamed_with_git,scripts/family.py:2906-2993at that head), andaddprints the rename back for one state and the move back for the next, so one writer takes them with the lines above. This issue lands after #767 as well as #670. Line numbers are at215e8102.git mv members Members(rv685's candidate 1; measured by rv685: its probe "staged, thenMembers/IRSSunstaged").git mvrewrote every section toMembers/<P>, and one member's addition under the new spelling was then unstaged (Members/IRSS), so its removal atmembers/IRSSstays staged. HEAD holdsmembers/IRSS; the index holds neither spelling of it._members_spellingsnever sees it: the HEAD pass (:3105-3109) skips a name that_spelled_targetcalls canonical, and_renamed_with_gitasks nothing about a section whose entry is gone (its conditions,:2930-2993). The rename back is printed, typed it exits 0, and it renames only the entries the index holds, so that member's section stays atMembers/IRSS.addthen goes through and commits that.gitmodules, and the gate refuses (rv685's table: gates 2/2/1,add0). This is (e)'s section left naming the variant, reached by the rename back. Direction: where a section of.gitmodules(the work tree's or HEAD's) gives<spelling>/<P>for a member the index no longer holds there, keep the move back's words and (e)'s section line, or say the member's removal is staged and must be committed or undone first. Prove it with rv685's state.git mvwould take (rv685's NIT 5; read from source, not run)._renamed_with_gitreturns None unless every entry under another spelling is under ONE spelling (len(spellings) != 1,:2952), so a file atMEMBERS/notesbeside the renamedMembers/<P>entries sendsaddback to the move back, whose text has (e)'s gap (the section left atMembers/<P>). rv685: "stricter thangit mvneeds" and "outside the issue's states". On a disk that keeps the two spellings apart,git mv -- Members members.renamingand the second step do not touchMEMBERS/notes, whichaddthen refuses as its ownfamily-members-dir-variant, with its own remedy. Direction: decide whether the rename back is printed for the spelling that holds a member's gitlink and the third spelling is left to its own remedy, one line each, or the third spelling is named first. A folding disk, where all three are one directory, is the case that needs care (not run: this host is case-sensitive).addrefuses, before the member is read and on every disk, any path under another spelling ofmembers/, in its index, in HEAD or on disk, and names a remedy per entry that never moves the members' checkouts (#362, #363, #364) #574's plaingit mvis refused in a cone-mode sparse checkout when an entry is on disk with its skip-worktree bit cleared (rv685's candidate 2: "Not run throughadd").addpicks the plaingit mvwhere the entry is on disk (_on_disk_as_named,:3095-3098) andgit mv --sparseonly wheregit ls-files -tsaysS(_skip_worktree,:2778-2785). An entry outside the cone that is on disk with the bit cleared is neither. Measured here with plain git 2.43.0 and a file entry (not a gitlink, not throughadd):git sparse-checkout init --cone,set other, the file recreated underMembers/IRSS/,git update-index --no-skip-worktreeon it, thengit mv -- Members/IRSS/f Members/IRSS/gexits 1 with "paths ... exist outside of your sparse-checkout definition, so will not be updated in the index", and the hint "Use the --sparse option". rv685's 3-step probe in a real cone checkout gave the same rc 1. Direction: treat an entry the sparse definition excludes asRENAME_SPARSE's, whatever its bit says: askgit sparse-checkout listor try thegit mv --dry-run, then print--sparse(git 2.38 or later, as family.py add: amembers/renamed withgit mvis renamed back in two steps through a free name, after what the disk lacks under it is put back, and a printed--sparsesays the git it needs, so no remedy fails as typed (#685) #767's text now says). Run it throughaddon a gitlink before the text says it.Lane: openreposhape-5 (openRepoShape-5)
brettheap commentedon Oct 17, 2026
CLAIMED — lane openRepoShape-5, session 50cede29-ab82-4ca3-9f67-d98e1a58c8d2@Eagle, 2026-10-17T10:26:59Z, for #761
Logged in
lanes/log/openRepoShape-5.mdat1b312a9a059374b6f86cdd2ece4c895786321653.The three reads (lane-collision-protocol Rule 1):
family.py add: a sparse checkout no longer decides whose a gitlink in…
Merge main bad05d2 (6626a3c..bad05d2, #684 among it) into #761
family.py add: a `members` file HEAD holds and a sparse checkout keep…
validate-family.py: the wrong-case move back mends `.gitmodules`, add…
tests: three more hand-worked `GIT_CONFIG_COUNT` appends go through `…
family.py add: the move back of a member's gitlink points its section…