Skip to content

bootstrap.py and validate-family.py (family root): a .gitmodules in the index or HEAD with one bad line is read as no sections where git reads those before it, so a no-credential run fails wrongly, and the value rules are written twice #856

Closed

Description

@brettheap

Goal: where git reads a family holder's .gitmodules from the index or from HEAD and takes the sections before a line it cannot parse, make bootstrap reads the same sections, and the rules that say which .gitmodules values git dies on are written once, or held to one answer by a test.

Filed by lane openRepoShape-5, from the checklist candidates of #768's PR (#819, writer f819: "a .gitmodules copy in the index or HEAD with one bad line is read as having no sections", measured on git 2.43 and 2.47.1; and rv819's NIT 7, which #819's fix round left: "the rules are written twice"). #819 lists the first as "Left out: a blob .gitmodules with a config syntax error: git reads the entries before the bad line; gitmodules_sections reads none, as before", and lane 5's #839 names it in its "Not here" and files it nowhere; this is the place for both.

Pinned copies. templates/family-root/scripts/bootstrap.py and templates/family-root/scripts/validate-family.py are pinned in every holder (FAMILY_COPIED_VERBATIM, scripts/shape_materialize.py:525). Bundle this with the other v1.1.1 edits to them (#768's PR #819, #762's PR #820 and #763's PR #822 edit one or both; #839 files more), so each holder takes one upstream-changed row per file.

Lands after: lane 5's #768 (PR #819, fix/768-gitmodules-values, head e1379a9d): the blob reading and the value rules are #819's, and main has neither in bootstrap.py. path:line is at e1379a9d, except where a line says main (validate-family.py:1144-1155, scripts/repo_shape.py:1088-1120, bootstrap.py:94 and validate-family.py:113), which are at main fc867207.

Lines

  • A .gitmodules blob with one bad line is read as no sections. The git half is measured here on git 2.43.0, the only git on this machine (f819 measured 2.43 and 2.47.1). A scratch holder had two members, Atlas and Nova; a seventh line, branch = "unterminated (an open quote), was added at the end of Nova's section and committed; then git submodule deinit -f --all and .gitmodules was removed from the work tree, the state where git reads the index's file, then HEAD's.

    The bootstrap half is read from source and from f819's measurement, not run here. gitmodules_sections (templates/family-root/scripts/bootstrap.py:360-396) lists the blob with git_out(["config", "-z", *source, "--list"]) (:384), which raises git-failed on the 128 and drops what git printed first (scripts/repo_shape.py:1088-1120 on main), and except Refusal (:386-387) returns every field empty, with unreadable set only where the work tree holds the file (os.path.isfile(root / GITMODULES), false here). With no credential, each row git left not checked out is then named "no section of .gitmodules gives that path" and the run fails, where git checked the members out and the credential was all that was missing. Direction: for a blob, keep what git printed before it died: run git config -z --blob <source> --list with subprocess.run as git_out does and, where it exits 128 with bad config line on stderr, hand the entries on stdout to gitmodules_entries as the sections git reads. ends_blob_reading (:448-456) already stops the reading at a key with no value, which is the same stop for a bad line. A bad line before the first section gives nothing, as git does.

  • The .gitmodules value rules are written twice, and a third time in the test. Read from source, not measured. Bootstrap holds NO_VALUE_REFUSED (:130), UPDATE_VALUES (:143), TAKES (:145), refused_value (:488) and git_bool (:529); the gate holds GIT_TAKES (templates/family-root/scripts/validate-family.py:1106), _values_refused (:1230) and _git_takes_bool (:1258); and tests/test_family_gitmodules_values.py:74 holds a TAKES of the texts both say. Git's rules (parse_config in submodule-config.c) move with git, and a change made in one script is a defect in the other until somebody makes it there too. Direction: decide first where one copy lives. scripts/repo_shape.py is the module both scripts import (bootstrap.py:94 and validate-family.py:113 on main), but it is a shape copy every project digest-pins, so a function added there puts an upstream-changed scripts/repo_shape.py row in front of every assembly root and every holder; scripts/shape_materialize.py:1178-1181 and :1953-1960 give that as the reason other shared helpers live elsewhere (there for a rule no project's copy asks; here both family scripts ask it, and an assembly root takes the row for nothing). The other ways: a module of the family root's own, listed in FAMILY_COPIED_VERBATIM so each holder gets it beside the two scripts, or two copies held to one answer by a parity test that runs one table of (key, value) cases through refused_value and _values_refused and fails where they differ. Whichever is chosen, the three copies above become one.

Not here: the gate's _head_gitmodules (validate-family.py:1144-1155 on main) refuses a HEAD blob git cannot list as gitmodules-unreadable, and that is right: it asks the file a recursive clone gets, which lands in the clone's work tree, where git dies reading it. Bootstrap's blob reading is for a holder whose work tree has no file. The two differ on purpose, and the gate is not a line of this issue. #819's other candidates are #839's (the credential advice) or are said in #839's "Not here" (submodule.<name>.active that is no boolean; _sections_listed reading a key with no value as the empty string).

Verify: tests/test_family_gitmodules_values.py (new in #819), tests/test_family_bootstrap_gate_order.py, tests/test_validate_family_unnamed_mounts.py, tests/test_family_head_lost_gitmodules.py and tests/test_pinned_copy_text.py pass on Linux. For the first line, a case beside test_a_key_with_no_value_ends_git_s_reading_of_the_index_s_file (tests/test_family_gitmodules_values.py:434 at e1379a9d): a plain clone whose work tree lost .gitmodules uncommitted, index and HEAD holding it with a line git cannot parse, no credential, the line at the end, after the first section, and inside a section; in each, git's submodule update and bootstrap agree on which rows are read, and the run ends as it does for a credential that is the only thing missing. A mutant that puts the except Refusal back as it is, caught. For the second line, the parity test above, or an identity assertion if the rules move to one module, and a mutant that changes one TAKES entry in one script, caught. No Windows or macOS run: CI is Linux only (#597).

Overlaps. #819 edits both scripts; #820 and #822 edit validate-family.py; #821, #823 and #827 edit scripts/repo_shape.py, which only the second line's first option touches. Lane 5's #839 (filed) works in the same two scripts, on the credential advice, run_member_target and _wrong_case_move. Both scripts are pinned copies, as said above. No README or handbook sentence is made false.

Parent: #459.

Milestone: v1.1.1. The first line needs a work tree without .gitmodules (deleted and not committed, or a sparse checkout that leaves it out), a bad line in the index's or HEAD's copy, and no credential; git's own error: line prints above the failure, so the person sees what is wrong. The second is a maintenance risk with no failure today. Neither is a regression, and neither blocks v1.1.0.

🤖 Generated with Claude Code

Activity

  1. added this to the v1.1.1 milestone on Oct 17, 2026
  2. brettheap commented on Oct 18, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-3, session b30e8b49-f249-4668-adb1-1dbfa93c09e1@Eagle, 2026-10-18T05:15:44Z, for #856

    Logged in lanes/log/openRepoShape-3.md at 2f6de52183f9cc9afa43d9f118c29b22406364ed.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#856:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#856"`, kept where the row or its body names `#856`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `856` as a whole token:
       none
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions