Repository navigation
bootstrap.py: the merge --ff-only, --continue and --abort lines it prints word a leg's path and an upstream as a shell reads them back, so a byte that is not UTF-8 is a printf and no longer escape text that names no ref (#932) - #944
Conversation
… it prints word a leg's path and an upstream as a shell reads them back, so a byte that is not UTF-8 is a `printf` and no longer escape text that names no ref (#932) step (a) printed `git -C <leg> merge --ff-only <upstream>`, and an operation's `--continue` and `--abort`, through `spelled(shlex.quote(...))`. `spelled` writes a byte that is not UTF-8 as #391's `\udcXX` text, and a control character, a line separator and a bidirectional control as the escape Python spells it, so for an upstream `origin/a<0xff>b` the line named no ref and git answered `not something we can merge`. `_paste_word` words one path or ref: exactly what it was where `spelled` has nothing to escape and the stdout can write the name, and otherwise `"$(printf -- '<octal escapes>')"`, which dash, bash and zsh turn back into the bytes. A pinned copy, so it imports nothing new from `repo_shape.py`. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-18T16:34Z) @codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Reviewer's GuideMakes bootstrap commands printed for manual pasting reliably round-trip leg paths and upstream refs through POSIX shells, especially for non-UTF-8 and control bytes, while preserving compatible output for already-safe names and adding comprehensive exact-output and integration coverage. Sequence diagram for pasteable bootstrap command generationsequenceDiagram
participant Bootstrap
participant PasteWord as _paste_word
participant Stdout
participant Shell
participant Git
Bootstrap->>PasteWord: _paste_word(path)
PasteWord->>Stdout: _prints_as_it_is(name)
alt name prints and reads back unchanged
PasteWord-->>Bootstrap: spelled(shlex.quote(name))
else name needs byte-safe representation
PasteWord-->>Bootstrap: "$(printf -- '<octal bytes>')"
end
Bootstrap->>Stdout: print merge or --continue/--abort command
Stdout-->>Shell: pasted command
Shell->>Git: execute with original path/ref bytes
Git-->>Shell: operate on intended leg or upstream
Flow diagram for choosing a pasteable shell wordflowchart TD
A["_paste_word(name)"] --> B{spelled text writes and reads back unchanged?}
B -->|Yes| C[Return spelled shlex quote]
B -->|No| D{os.fsencode round trip succeeds and name does not end in LF?}
D -->|No| E[Keep spelled text]
D -->|Yes| F[Encode non-bare bytes as three-digit octal escapes]
F --> G[Return double-quoted printf command substitution]
C --> H[Use in printed bootstrap command]
E --> H
G --> H
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Independent review of #944 (#932) at No finding is a MUST. The FindingsSHOULD 1. A word that begins with
SHOULD 2 (landing). The clean merge with #896 keeps the old form in a new line.
SHOULD 3 (follow-up). The NOT INITIALIZED paste line is still neither quoted nor worded.
NIT 1 (check 4, the names with no word): keeping today's text is right, but the body should say so against the Goal.
NIT 2.
NIT 3. On POSIX the round-trip guard (
NIT 4. Two mutants survive. All 124 tests in the new file and the two changed test files pass with each.
NIT 5. Stale wording.
Checked and true
The commit is authored Could not verify
Checklist candidates
|
…ame the commits "a branch spelled like main" holds, and the docstrings say what making a branch hides, so no line over an unborn twin is false (#896) Part 1 of the fix round on #948, from rv896's review (issuecomment-6252823490), which found the change READY with four NITs that need nothing from #944. NIT 1. `TWIN_RISKS["make"]` said making the branch could hide "the commits Main holds"; an unborn twin holds none. It now says "the commits a branch spelled like main holds", which is true of a packed twin, an unborn twin and a third spelling beside either. NIT 2. The missing-branch refusal told the person to check out the pin and never printed it. Its first line now reads `branch main does not exist (the pin is <sha12>), and the checkout is on Main, ...`. No refusal #896 did not add is changed. The packed-twin test takes the sha from the printed line to run the way out. NIT 3. Two `_folded_twin` rows, `refs/notes/Main` and `refs/tags/xMain` against `main`, both false. Each has 11 characters before `Main`, as `refs/heads/` does, so a mutant that drops the `refs/heads/` guard and keeps the 11-character slice answered true for both and survived the 24 tests; the two rows kill it. NIT 4. The module docstring says what each act could do: a branch MOVED as `main` could be the ref HEAD stands on, with the index left at the old commit, and one MADE as `main` could hide the commits the twin holds behind the new ref. `_say_twin`'s docstring no longer says "packed or not": a loose `Main` on a disk that folds case makes `refs/heads/main` resolve, so the missing-branch path never runs for it. The `switch` line and the docstrings that compare it with `move_back_to_pin` are not touched: part 2 switches them to #944's `_paste_word` once #944 lands. Cognitive complexity is unchanged: place_at_pin 6, fast_forward_to_pin 15, create_at_pin 13, _folded_twin 3, _say_twin 1. (The first commit's message says 14, 6 and 12; those were a hand count that missed `why or []`.) Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
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 1 hour by commenting |
…h and the branch with `_paste_word`, so a branch that is not UTF-8 pastes into a shell as the bytes it is (#896) Part 2 of the fix round on #948, from rv896's SHOULD 1, now that #944 (#932) has landed as 450279c and `_paste_word` is on main. The first commit's merge of origin/main is the commit before this one. `_say_twin` built its command with `spelled(shlex.quote(...))`, the form #932 replaced. For a branch named with a byte that is not UTF-8 that printed `git -C spec switch 'a\udcffb'`, and `/bin/sh` reads that word as the eight characters it holds, so the pasted command named no branch. The command is now `git -C {_paste_word(path)} switch {_paste_word(branch)}`, as #944's hand-over gave it: a name that prints as it is stays `shlex.quote`d, and one with a byte that is not UTF-8 is a `printf` that prints the bytes. `_say_twin`'s docstring and `test_the_way_out_is_quoted_for_a_shell_...` say so, where they said the command was printed as `move_back_to_pin` printed it before #944. Tests: one end-to-end case with a tracking branch `a<0xff>b` and HEAD on its twin `A<0xff>b` (T7, `--branch`): bootstrap leaves the leg, the printed command is `git -C spec switch "$(printf -- 'a\377b')"`, run through `/bin/sh -c` as printed it puts HEAD on `refs/heads/a<0xff>b` byte for byte, and the next run fast-forwards it. Five unit rows read the two words of the command back through `/bin/sh` and compare them with the bytes of the names (an ordinary pair, a path with the byte, a branch with the byte, both with a leading dash, a path with a percent). Dropping `_paste_word` from the path, from the branch, or restoring the old form each fails 3, 3 and 5 of the 32. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…racking branch only in case (`Main` for `main`) is left where it is, so on a disk that folds case `branch -f main` no longer moves the ref HEAD stands on (#896) (#948) **Head 4541529** (fix round 1: part 1 `48222b5e`, then the merge `60df34fc` of origin/main `450279ca` (#944), then part 2), on the first head `80353dd1`, on origin/main `50178305` (the v1.1.1 release commit). origin/main is now `450279ca`: `git merge-tree --write-tree` of this head against it is clean, tree `aabc059b`. One file of `templates/` changes: `templates/assembly-root/scripts/bootstrap.py`. One new test file. **Fix round 1, part 2 (rv896's SHOULD 1, one commit after the merge of main, `45415296`).** #944 (#932) landed as `450279ca`, so `_paste_word` is on main. `_say_twin`'s command is now `git -C {_paste_word(path)} switch {_paste_word(branch)}`: for a branch named with a byte that is not UTF-8 the old `spelled(shlex.quote(...))` form printed `switch 'a\udcffb'`, which `/bin/sh` reads as the eight characters it holds, so the pasted command named no branch. `_say_twin`'s docstring and the docstring of `test_the_way_out_is_quoted_for_a_shell_...` now say `_paste_word`, where they said the line was printed as `move_back_to_pin` printed its own before #944. A non-UTF-8 twin test runs the printed `switch` through `/bin/sh -c` as printed, and five unit rows read each word back through `/bin/sh`. **Fix round 1, part 1 (rv896's NITs 1 to 4, one commit, `48222b5e`).** rv896 found this change READY (issuecomment-6252823490). Part 1 takes the four NITs that need nothing from #944: the `make` risk now says "the commits a branch spelled like main holds" (an unborn twin holds none); the missing-branch lines print the pin's sha, `branch main does not exist (the pin is <sha12>)`; two `_folded_twin` rows, `refs/notes/Main` and `refs/tags/xMain`, kill the one surviving mutant; the module and `_say_twin` docstrings say what MAKING a branch hides. The `switch` line was left for part 2, below, which switches it to #944's `_paste_word` now that #944 has landed. A pinned copy: every project's `bootstrap.py` takes an `upstream-changed` row, and re-pins it. Where HEAD's ref and `refs/heads/<branch>` are equal under `str.casefold()` but differ in bytes, bootstrap leaves the leg. No ref moves and none is made. It says where the leg is, why it is left, and the way out. - `_folded_twin(current, branch)` says whether HEAD's ref is such a twin. A detached HEAD (`None`) and a ref outside `refs/heads/` are never twins. It compares the names after `refs/heads/`, so a name held as surrogate escapes (#704) raises nothing. - `_say_twin(label, path, branch, current, state, act)` prints the three lines. Each of the three path functions asks the helper first, before any move, and grows by one `if`. They take the leg's `path` for the command, as `move_back_to_pin` does; `checkout_tracking_branch` passes it at its three call sites only (its `(sub / ".git").exists()` lines are untouched). - The words hold on a disk that keeps case too, where `Main` and `main` are two branches. They never say the two are one ref: they say that a disk that folds case (macOS, Windows) reads them as one branch, and what that could do. Before (all three said the leg was on `Main`, which keeps its commits (a branch with no commit yet, for the unborn twin below), and moved or made `main`): T7 (main behind the pin) branch main fast-forwarded <old> -> <pin>, the pin; ... the leg was on Main, which keeps its commits; it is on main now P9 (main at the pin) on main at <pin> (branch tip == pin) the leg was on Main, which keeps its commits; it is on main now missing branch branch main created at <pin> - at the pin the leg was on Main, which keeps its commits; it is on main now Now (spaces and quoting as printed; the leg is `spec`): T7: [spec] spec: branch main tip <old> is behind the PIN <pin>, and the checkout is on Main, which differs from main only in case a disk that folds case (macOS, Windows) reads Main and main as one branch, so moving main could move the ref HEAD stands on and leave the index at the old commit; the leg is left as it is run `git -C spec switch main`, which moves no branch, and then run bootstrap again. P9: [spec] spec: branch main is at the pin <pin>, and the checkout is on Main, which differs from main only in case a disk that folds case (macOS, Windows) reads Main and main as one branch, so the leg is already on main there, and bootstrap would say it left a branch that keeps its commits; the leg is left as it is run `git -C spec switch main`, which moves no branch, and then run bootstrap again. missing branch: [spec] spec: branch main does not exist (the pin is <pin>), and the checkout is on Main, which differs from main only in case a disk that folds case (macOS, Windows) reads Main and main as one branch, so making main could hide the commits a branch spelled like main holds behind the new ref; the leg is left as it is not moving the checkout off it. Keep any commit made there on a branch whose name differs from main by more than case, check out the pin in the leg, and run bootstrap again. T7 and P9 give `git -C <leg> switch <branch>`, the path and the branch each one `_paste_word` (#932), as `move_back_to_pin`'s path and upstream are: a name that prints as it is stays `shlex.quote`d, and one with a byte that is not UTF-8 is a `printf` that prints the bytes. On a disk that folds case it re-spells HEAD and moves no file, and the next run takes T7's on-branch `merge --ff-only` path or P9's no-op checkout. Where the two are branches, it puts the leg on the real `main`, and `Main` keeps its commits. Every other state prints what it printed: HEAD on `refs/heads/main` itself, on `feature`, on `mains`, detached, on a ref outside `refs/heads/`. CI is Linux only, so a disk that folds case is emulated, as the issue's own probe did: a symlink `.git/refs/heads/Main -> main`, which git's files backend reads through, and HEAD on `refs/heads/Main`. T7 is the issue's state: `main` loose and behind the pin, the pin's commit adding `NEW-CONTRACT.md` and editing `README.md`. At `50178305`: before: HEAD -> refs/heads/Main | Main = main = 536671ad3925 | pin = f27b6a92a433 | status '' bootstrap: branch main fast-forwarded 536671ad3925 -> f27b6a92a433, the pin; origin/main is 536671ad3925, which the pin deliberately does NOT follow the leg was on Main, which keeps its commits; it is on main now after: HEAD -> refs/heads/main | HEAD = main = f27b6a92a433 | status 'D NEW-CONTRACT.md;M README.md' At this head (a second scaffold, so other shas): before: HEAD -> refs/heads/Main | Main = main = bf6b7be3d5fa | pin = 19051708a6c9 | status '' bootstrap: the three T7 lines above after: HEAD -> refs/heads/Main | Main = main = bf6b7be3d5fa | status '' P9 on the base printed `on main at <pin>` and the `the leg was on Main` line; at this head it prints the three P9 lines, and HEAD, `main` and the status are as before. The way out, `git -C spec switch main` run as printed, puts HEAD on `refs/heads/main`, moves no branch, and the next run prints `fast-forwarded` (T7) or `on main at <pin> (branch tip == pin)` (P9), with `git status --short` empty. At `50178305`, git 2.43.0 and 2.47.1, the same result. `checkout -q --no-overwrite-ignore -b main <pin>`, from HEAD on `Main`, `main` not there: - **A packed twin with commits** (`git pack-refs --all`, so no loose `Main`, no `main`). Before: HEAD on `Main`, HEAD = `Main` = the twin's commit, `main` missing, status clean. The checkout exits 0 and writes the loose file `main`. On a disk that folds case that file is the file `Main` reads, so `Main` now names the pin, and the twin's commit is in no branch and no ref (`git branch -a --contains`, `git for-each-ref --contains`: nothing); only HEAD's reflog lists it. **This is the harm.** The emulation makes the link after the checkout: a link made before a packed `Main` is a DANGLING link, git reads `Main` as missing through it (it falls back to packed-refs only when `lstat` says ENOENT, and a folding disk would say that), HEAD reads as unborn, and the staged `g.txt` is left. That before-state is not a folding disk's, so the probe has no link before the checkout, and the committed packed test has none at all: a real `Main` with a commit, packed, is the state of a disk that keeps case, and the lines are the same on both. - **An unborn twin** (HEAD on `Main` with no commit, the dangling link `Main -> main`, which a folding disk reads as one missing ref). The checkout moves HEAD to `main` at the pin and the index is consistent. Nothing is hidden: the twin holds no commit. **It is harmless.** It is refused all the same: one rule, one helper, and no state of HEAD to branch on; `head is not None` in the missing-branch path would exempt it, if the coordinator prefers that. The words follow the packed probe: `switch main` is no way out of a leg with no `main` on a disk that keeps case, so the line says to keep the commits on a branch whose name differs from `main` by more than case, check out the pin (whose sha the first line prints), and run bootstrap again. The risk says "the commits a branch spelled like main holds", which is true of a packed twin, of an unborn twin (it holds none, so nothing is hidden) and of a third spelling beside either (rv896's probe: an unborn `Main` beside a packed `MAIN` hides that commit the same way). The committed packed test runs the way out: `git branch kept-from-Main`, `git checkout --detach <the printed sha>`, bootstrap prints `branch main created at <pin> - at the pin`, `kept-from-Main` and `Main` both hold the commit. `tests/test_bootstrap_casefold_twin.py`, 32 tests. The leg set-up is `test_bootstrap_after_bump.py`'s fixtures, `bumped` and `fast_forwarded`, and `test_bootstrap_leg_moves.py`'s `typed` and `main_of`; the helper `bootstrap_module` is `test_bootstrap_branch_bytes.py`'s. Every test uses the module's own local bare remotes. Each symlink is `os.symlink`, and the test skips on Windows, and where it raises `OSError` (a disk that folds case refuses the link: `Main` is `main`), with the reason. A test that makes a real `Main` skips on a disk that folds case. Every assert on the output compares step (a)'s spec-leg lines exactly. 1. T7 under the emulation: `main` unchanged, HEAD still `refs/heads/Main`, `git status --short` empty, the three lines exact. 2. The way out runs as printed, then bootstrap again: `fast-forwarded`, HEAD `refs/heads/main` at the pin, status empty. 3. P9 under the emulation, the lines exact, and its way out. 4. The missing branch: an unborn twin under the emulation, and a packed twin with a commit, with the way out run. 5. A disk that keeps case: a real loose `Main` with a commit of its own and HEAD on it, T7's state. The same lines. The way out puts the leg on the real `main`, bootstrap fast-forwards it, `Main` keeps its commit. 6. Unchanged byte for byte: HEAD on `main` itself, on `feature` and on `mains` (3 tests). 7. `_folded_twin`'s table (15 rows): `Main`, `MAIN`, the twin the other way, the branch itself, `Main` against `Main`, detached, `refs/remotes/origin/main`, another branch, a longer name, `refs/Heads/main`, `refs/notes/Main` and `refs/tags/xMain` (each 11 characters before `Main`, as `refs/heads/` is), a final sigma against a sigma (`casefold` folds it, `lower()` does not), and a name that is not UTF-8, itself and as a twin. Plus three tests of `_say_twin` itself: the command is shell-quoted as before for an ordinary pair, a name that is not UTF-8 or holds an ESC is spelled (output is ASCII, no raise), and (part 2) five rows read the two words of the command back through `/bin/sh` and compare them with the bytes of the names: an ordinary pair, a path with the byte, a branch with the byte, both with a leading dash, and a path with a percent. 8. (Part 2) A tracking branch `a<0xff>b` and HEAD on its twin `A<0xff>b` (T7, `--branch`, POSIX, on a disk that takes the byte; skipped on Windows, and on macOS, whose disk refuses the byte): the lines name both spelled, the printed command is `git -C spec switch "$(printf -- 'a\377b')"`, run through `/bin/sh -c` as printed it puts HEAD on `refs/heads/a<0xff>b` byte for byte, and the next run fast-forwards the branch. **On the base (`50178305`, the first 26 tests copied in): 23 failed, 3 passed.** The three that pass are case 6, by design. Part 2's new test cannot run on the base (it imports `test_bootstrap_pasteable_lines.py`, which #944 added); on the merged tree before the part 2 change it failed, at the command: `- git -C spec switch "$(printf -- 'a\377b')"` expected, `+ git -C spec switch 'a\udcffb'` printed. **At this head: 32 passed**, on git 2.43.0 and 2.47.1. | mutant | result | |---|---| | compare byte for byte again (`_folded_twin` never true) | 11 failed | | the check dropped from T7 / from P9 / from `create_at_pin` | 3 / 1 / 2 failed | | `casefold()` -> `lower()` | 1 failed (the sigma row). Equivalent for ASCII; the sigma row tells them apart, so no `ss` pair was needed | | the `None` guard dropped (`None.startswith`) | `make bootstrap`'s first run raises in the fixture: 9 errors and 1 failed | | a twin with `current` `None` (returns true for it) | the same 9 errors and 1 failed | | `switch` -> `checkout` in the printed way out | 4 failed (the exact-lines tests). Case 2, which only runs the printed command, still passes: `git checkout main` does the same here | | the `refs/heads/` guard dropped (whole refs compared) | 1 failed (`refs/Heads/main`) | | `held != branch` dropped (the branch itself a twin) | 7 failed | | the path / the branch not shell-quoted | 1 failed each | | the twin / the branch not spelled | 1 failed each | | the missing-branch path printing the `switch` command | 3 failed | | the missing-branch way out saying `a branch` for `a branch whose name differs ... by more than case` | 3 failed | | **R2** (round 1): the `refs/heads/` guard dropped, the 11-character slice kept | **24 passed on the first 24 tests: it survived. 2 failed on the 26 (`refs/notes/Main`, `refs/tags/xMain`): killed.** | | round 1's re-runs on the 26: the check dropped from `create_at_pin`; `casefold()` -> `lower()` | 2 failed; 1 failed | | **part 2:** `_paste_word` dropped from the path / from the branch / the old `spelled(shlex.quote(...))` form restored | 3 / 3 / 5 failed of the 32 (the byte-path rows; the end-to-end test and the byte-branch rows; all of them but the ordinary pair). None survives | The host's load was 85 to 270, so the coordinator scoped them (one pytest process at a time, 15:53Z and 16:15Z). - **git 2.43.0**, `tests/test_bootstrap_casefold_twin.py` and every `tests/test_bootstrap*.py` (11 files, the issue's `test_bootstrap_after_bump.py`, `test_bootstrap_leg_moves.py` and `test_bootstrap_non_utf8_branch.py` among them), `test_family_add_spelling_leftovers.py`, `test_pinned_copy_text.py` and the trio (`test_repo_hygiene.py`, `test_agents_md_codes.py`, `test_cli_reference.py`): **751 passed, 3 skipped** (the three skip `test_bootstrap_ref_store.py:102`: 2.43 makes no reftable repository) in 14:47. Then `test_bom_reads.py` and `test_validator_child_encoding.py`: **188 passed**. - **git 2.47.1**, the new file, `test_bootstrap_leg_moves.py`, `test_bootstrap_after_bump.py` and the trio: **248 passed** in 3:40. - **Round 1, part 2, at the merged tree** (the load was about 50, two processes at a time): git 2.43.0, the new file, `test_bootstrap_pasteable_lines.py`, `test_bootstrap_non_utf8_branch.py`, `test_bootstrap_leg_moves.py`, `test_bootstrap_after_bump.py` and the trio: **329 passed** in 7:04. git 2.47.1, the new file and `test_bootstrap_pasteable_lines.py`: **72 passed** in 2:09. Both ran before the five unit rows were added (a change to the test file alone); the new file was then run again on both: **32 passed** each. - **Round 1, at the part 1 tree** (the load was about 40, two processes at a time): git 2.43.0, the new file, `test_bootstrap_leg_moves.py`, `test_bootstrap_after_bump.py` and the trio: **250 passed** in 3:44. git 2.47.1, the new file: **26 passed** in 20 s. - **The predicted merge with origin/main `64464d31`** (a commit of tree `d7d8ca11`, never pushed): the new file, the trio and `test_release_workflow.py`: **231 passed** (git 2.43.0). - A first run of 115 files in 3 shards (git 2.43.0) was stopped at 35%, 28% and 45% when the load reached 270: about 3,100 tests, none failed. It is not counted. **Left to CI's `tests` job, which runs the whole suite on the head:** every file not named above. Of the files `git grep -l -E 'assembly-root/scripts/bootstrap|make bootstrap|bootstrap\.py' -- tests` names, these 66 were not run: test_adopt_attributes_eol_block.py, test_adopt_clone_template.py, test_adopt_e2e.py, test_adopt_ignored_shape_files.py, test_adopt_mount_ignored.py, test_adopt_nested_attributes.py, test_adopt_pins.py, test_adopt_plan_yaml_quoting.py, test_adopt_reencoded_files.py, test_adopt_relative_submodule_url.py, test_adopt_root_manifest.py, test_adopt_root_symlinks.py, test_adopt_shape_pin_and_work_dir.py, test_adopt_submodules.py, test_adopt_verify.py, test_bump_leg.py, test_bump_leg_dry_run_truth.py, test_bump_leg_rollback.py, test_bump_leg_unreadable.py, test_doctor_legs_members_next.py, test_doctor_machine_row.py, test_doctor_next_is_a_command.py, test_doctor_paste_through_git.py, test_doctor_pin_restore_merges.py, test_family.py, test_family_bootstrap_gate_order.py, test_family_bump_remove_rollback.py, test_family_gitmodules_values.py, test_family_head_lost_gitmodules.py, test_family_holder_bootstrap_texts.py, test_family_makefile_siblings.py, test_family_members_not_a_list.py, test_family_siblings_placement.py, test_leg_path_yaml_scalars.py, test_legs_readers_not_a_list.py, test_load_yaml_refusals.py, test_naming_policy.py, test_non_utf8_name_helpers.py, test_park_resume_targets.py, test_push_branch_refspec.py, test_scaffold_e2e.py, test_scaffold_ignored_shape_files.py, test_scaffold_leg_path_collisions.py, test_scaffold_manifest_values.py, test_scaffold_mount_ignored.py, test_setup_by_hand_commands.py, test_setup_project_py.py, test_setup_sh.py, test_shape_doctor.py, test_siblings_family_name.py, test_siblings_inputs.py, test_template_text_placeholders.py, test_update_shape.py, test_update_shape_add.py, test_update_shape_apply_lines_and_headers.py, test_update_shape_kept_helper.py, test_update_shape_landing.py, test_update_shape_merged_away.py, test_update_shape_pr_base.py, test_validate_family_branch_step.py, test_validate_family_member_path_glob.py, test_validate_family_remedies_typed.py, test_validate_family_typed_states.py, test_validate_pins_lockstep_lines.py, test_validate_workflow_gitmodules_urls.py, test_windows_paths.py. On git 2.47.1 the 2.43 list's other files were not run either. - origin/main `450279ca` (#944 landed): this head merges clean, tree `aabc059b`. The merge commit `60df34fc` itself is a clean merge, tree `664513ff`, as predicted. - Slice D's head `0ef875c0` (#958): clean, tree `af0a54b7`. - The other lanes' open branches, `fix/415-legs-members-next-tests`, `fix/430-a-bump-leg-unsearchable`, `fix/879-holder-remedies-compose`, `fix/880-check-plan-name-docstring`, `fix/884-ignored-depth`, `fix/885-one-line-install-release-notes`, `fix/897-kept-copy-reraise` and `fix/898-bump-leg-reachability`: none touches `bootstrap.py` or this test file, and each merges clean with this head. `l3-677-pins-hand-step-wip` and `l3-678-naming-checkout-wip` conflict, in `shape-doctor.py`, `validate-pins.py` and tests this change does not touch, and conflict the same way with origin/main itself. - Slice D's earlier local head, `df1bb186`, conflicted with the first head (both added a block after `_on_a_branch`); it was moved at `0ef875c0`, which merges clean. - **Docs.** `bootstrap.py`'s module docstring (step (a)) and the docstrings of the three path functions and `_move_and_check_out` say the rule. I grepped `AGENTS.md`, `README.md`, `docs/`, the assembly root's `AGENTS-shape.md` and README, and the family root's, for a sentence about step (a)'s paths. None is false: each says a leg is placed `only where that loses no commit, and otherwise ... left where it is, saying why`, or `or says why not`, and a twin leg is left, saying why. Nothing else was edited. - **Cognitive complexity (S3776, 15 allowed), counted by hand and with an AST counter that follows Sonar's rules** (each `if`, `elif`, `else`, `for`, `except` and ternary, plus nesting; one per run of one boolean operator): `place_at_pin` 5 -> 6, **`fast_forward_to_pin` 14 -> 15, at the limit**, `create_at_pin` 12 -> 13, `_folded_twin` 3, `_say_twin` 1, `checkout_tracking_branch` 13 (unchanged). If Sonar counts one more in `fast_forward_to_pin`, the cheapest cure is a helper for its `note` (two points). Round 1 changes only words, so none changes. The commit message of 80353dd says `14, 6 and 12`; that was my hand count before the counter found the `why or []` argument in `fast_forward_to_pin` and `create_at_pin`. The numbers here are right, and the second commit's message says so; the squash message should not repeat the first commit's sentence. - **Width.** Every new line is within 79 columns in both files. No duplicated block; no `assert` joins conditions with `and`. - **One rule for all three paths,** the unborn twin included (see the probe). rv896 ruled to keep refusing it and not to add `head is not None`: an unborn twin can stand beside a packed third spelling whose commit `checkout -b` hides, and the refusal costs one step (`git -C spec checkout --detach <pin>`, then bootstrap). - **`casefold()` can over-match a real disk:** `ss` and the sharp s fold under it, which a disk's own simple folding may not do. The leg is then left, with true words, where the disk would not need it. - **The rule is by name, so a reftable leg is left too,** where `Main` and `main` are two refs on any disk. The way out is true there as well. - **NFC and NFD twins are out of scope** (below). 1. **An NFC/NFD twin does the same harm, and is not caught.** APFS also reads a name's NFC and NFD forms as one file. Emulated as the issue's probe was (a symlink from the NFD name to the NFC branch `caf` + U+00E9, HEAD on the NFD name, `--branch` the NFC name, git 2.43.0), at `50178305` AND at this head: `branch caf<e9> fast-forwarded <old> -> <pin>`, `the leg was on cafe<U+0301>, which keeps its commits; it is on caf<e9> now`, status `D NEW-CONTRACT.md` and `M README.md`. `casefold()` does not normalize; `unicodedata.normalize("NFC", ...)` in `_folded_twin` before the compare is the one-line cure, with a test. 2. **`bump-leg.py` and `family.py` compare the root's or the holder's branch with `tracking_branch` byte for byte** (`branch == project.tracking`, `branch != family.tracking()`): on a disk that folds case a root on `Main` is on `main`, and the pull-request-only refusal (`bump-leg-on-tracking-branch`, `family-on-tracking-branch`) is not given. Reasoned from the code; not probed. 3. **A HEAD on a ref whose directory is spelled otherwise** (`git symbolic-ref HEAD refs/Heads/main`) is a twin on a disk that folds case, and is not one here: the rule leaves refs outside `refs/heads/` alone, as the brief says. Reasoned; very rare. 4. **From rv896, for later:** a follow-up for `create_at_pin`: the same lost-commit harm happens from a HEAD detached at the pin over a packed `Main` and no `main`, which the rule does not see (it asks only about HEAD's ref); before `checkout -b`, ask `git for-each-ref refs/heads/` for any name that casefolds to the branch, packed or loose. Review: rv896, lane 5's independent reviewer, not the writer (w896): READY at `80353dd1` (issuecomment-6252823490), and its re-check of fix round 1 READY at `45415296`, the head that lands (issuecomment-6253509985), with no MUST and no SHOULD. Its one NIT, a leading `=`, is #961's, open as #973. CI (Brett Heap's ruling 27): H0 is the head 4541529 itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 450279c; and the local gate passed on its predicted squash tree 67a22e1 over main 3db58c4: the bootstrap_casefold_twin, repo_hygiene, agents_md_codes, cli_reference sets, 202 passed. Landed on Brett Heap's word of 2026-10-18T14:45:28Z, relayed to every lane by lane 3 (#473 issuecomment-6251255596): "merge v1.2.0 PRs once reviewed and green". Closes #896 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… of the bytes git is handed (UTF-8 on Windows), and the NOT INITIALIZED command names a leg's path as one word after a `--`, with `./` before a `:` (#961) (#973) #944 (#932) made each command step (a) prints for a person to paste name its leg path and its upstream as ONE shell word, through `_paste_word`. rv932's independent review of it (#944 issuecomment-6252637720) left a SHOULD and four NITs in that function, and #961 filed them, with a SHOULD for the one printed command #944 did not word: the NOT INITIALIZED line. This takes them all, in `templates/assembly-root/scripts/bootstrap.py`, `tests/test_bootstrap_pasteable_lines.py`, `tests/test_assembly_root_unreadable_paths.py` (#958's file), `tests/test_bootstrap_spelling.py` and the docstring of `tests/test_bootstrap_after_bump.py`. The branch is restacked on main `af0e60f2`, which holds #958 (#430 slice D, which moved the NOT INITIALIZED line into `_initialized`): #958's final head, then its squash, then main (nothing newer). The squash's tree is identical to #958's final head, so the merges brought in no code of their own. Against main this PR changes those five files and no other, and `git diff -U0` against main has the same changed lines as this PR's own commits on #958's head `7e87e05f` (checked from objects, see "What this adds to main"). **1. A leading `=` (SHOULD 1).** `_paste_word` quoted with `shlex.quote`, which leaves `=x` bare, and zsh expands a bare `=word` to the path of a command. - Before, read back by `/usr/bin/zsh` 5.9 (`printf %s <word>`): `=x`, `=ls`, `==x` and `=%` did not print themselves; `=ls` was `/usr/bin/ls` and `=x` was `zsh: x not found`. dash and bash read them all. - After: the word is `'=x'`, written locally with `quoted.startswith("=")`, as `repo_shape.shell_quoted` quotes it (#486). `bootstrap.py` imports nothing new from `repo_shape.py`, and a test holds the import set. A word `shlex.quote` quotes already (`=a b`) is not quoted again, and `a=b` stays bare. - The test row `("=x", "=x")` is now `("=x", "'=x'")`, in its own test, because the one it was in asserts "byte for byte what it was". - rv932's probe (every byte 0x01 to 0xff in seven places, about 160 special names, 3,194 names, each word read by dash, bash and `/usr/bin/zsh` under `LC_ALL=C` and `C.UTF-8`, as `set -- <word>`, one argument and the bytes git is handed): 450 mismatches at `450279ca`, all zsh and all a leading `=` (225 names in each locale), and **0 at the first head**, with 603 names that begin with `=` among those read. The 4 names that end in a line feed have no word (item 3). **2. The codec (NIT 2).** `_prints_as_it_is` asked the stdout's codec only, but git is handed another set of bytes. - Before: under a latin-1 or cp1252 stdout, `origin/café` was the quoted word, printed as the byte `e9`, where the ref is `c3 a9`. Under a latin-1 filesystem and a UTF-8 stdout it was the other way round. - After: a non-ASCII word is kept quoted only where `text.encode(stdout's codec)` equals the bytes git is handed (`_handed_to_git`: `os.fsencode`'s on POSIX, UTF-8 on Windows, item 8). Otherwise it is the `printf` word, which is ASCII. An ASCII word skips the comparison, so `origin/main` stays bare on any stdout. `spelled`'s check comes first. - **A decision:** the brief says to compare with `sys.getfilesystemencoding()`. `os.fsencode` is that codec with its error handler, and it is what `subprocess` hands git on POSIX, so comparing the two byte strings asks both at once and cannot disagree with what git is given. It is also the call a test shims in process (`LegacyWindowsOs` does, #944). **3. The round-trip guard (NIT 3).** The guard `os.fsdecode(raw) != name` kept the escape text for `sp\udcc3\udca9c`, though `"$(printf -- 'sp\303\251c')"` reads back exactly the bytes git is handed (the directory `spéc`). - After: no name is read back. git IS handed those bytes, so a name `os.fsdecode` would read as another has its word. A name that ends in a line feed keeps today's text, as the brief says. (On Windows the guard was replaced by item 8.) - **An addition inside "what can be pasted":** a name with a NUL has no word either. `$(...)` drops a NUL, so the old word `"$(printf -- 'a\000b')"` printed `ab` in dash and bash (zsh keeps it). It keeps today's text now. It cannot be reached from a real leg path or ref, so this is a guard and a unit row, nothing more. **4. R6 and R8 (NIT 4).** Two unit rows, in process: - R6, `except UnicodeEncodeError` alone: a `sys.stdout` stub whose `encoding` is `no-such-codec` (a `LookupError`, not a `UnicodeEncodeError`); the word is the `printf` word and nothing raises. - R8, `name.encode("utf-8", "surrogateescape")` for `os.fsencode`: a shim `os` whose `fsencode` is latin-1, and `café` + tab; the word is `"$(printf -- 'caf\351\011')"`, and `/bin/sh` prints the shim's bytes. **5. Stale words (NIT 5).** `tests/test_bootstrap_after_bump.py`'s docstring no longer says the command is "spelled" (#391): the names are, the command is `_paste_word`'s. The pasteable test file's docstring now says what the rule is, and which tests run what was printed and which ask the helper. The module paragraph says where the stdout cannot write the name, the leading `=`, the new command, and the names with no word. `_prints_as_it_is` and `_paste_word` have new docstrings. **6. The NOT INITIALIZED command (SHOULD 3).** `_initialized` printed `git submodule update --init {spelled(path)}`, bare, for a leg that is not there. - Before, pasted from the root (rv973 measured 40 awkward paths in sh, dash, bash and zsh): `my spec` was two pathspecs; a path with an ESC, a U+2028, the byte 0xff, `%s` or a tab was its escape text, which names no path; `sp'ec` was a syntax error in every shell; `=spec` failed in zsh (`zsh:1: spec not found`); `-spec` was an option. - After: `git submodule update --init -- <word>`, the path as `_paste_word` words it, after a `--`. The leg is checked out at its pin again, in every shell and on git 2.43 and 2.47.1 (rv973: 40 of 40). The label `[spec] <path>:` is still spelled. - A path that is not a name `printf` can print (a lone surrogate, a NUL, a final line feed) keeps its text, as in item 3. - rv973 asked for a landing condition: the PR does not land without this commit. **7. A path that begins with `:` (rv973's NIT 1 on the staged commit).** Git reads a word that begins with `:` as pathspec magic, even after the `--`, so `--init -- :spec` matched nothing and the leg stayed empty (all four shells, git 2.43 and 2.47.1). `_initialized` words `./:spec`: `./` goes inside the one word, so a path that needs quoting is `'./:my spec'` and a byte path is `"$(printf -- './:sp\377ec')"`. Only this command has it: `git -C :spec` is a directory, not a pathspec. **8. Windows: the bytes are UTF-8 (rv973's NIT 1 and NIT 2).** git for Windows is handed UTF-16 and stores UTF-8, and Git Bash reads a paste as UTF-8. - `_handed_to_git(text)` is `os.fsencode(text)` on POSIX and strict `text.encode("utf-8")` where `os.name == "nt"`. `_prints_as_it_is` compares the stdout with it, and `_paste_word` makes its `printf` bytes from it. - Before, under `PYTHONLEGACYWINDOWSFSENCODING=1` (where `os.fsencode` is the code page's): on main a UTF-8 stdout got `"$(printf -- 'origin/caf\351')"` for `origin/café` and `\2005` for `€5`, which Git Bash holds as other names, where the quoted word was right; and a codec rule made of `os.fsencode` would have given a cp1252 stdout the same kind of word (it was the staged commit's case, rv973's NIT 2). A lone surrogate had a word of three junk bytes in the default mode. - After: the quoted word stays on a UTF-8 stdout, the `printf` word is UTF-8 in either mode, and a lone surrogate, which has no UTF-8 bytes, keeps its text. The Windows read-back guard `os.fsdecode(raw) != name` is gone, and with it the one `os.name` test in `_paste_word`. POSIX is unchanged. - I have no Windows host: the evidence is in-process shims, as in #944. `tests/test_bootstrap_pasteable_lines.py` is now 128 tests (45 in #944). `tests/test_assembly_root_unreadable_paths.py` has 11 more rows, and `tests/test_bootstrap_spelling.py`'s NOT INITIALIZED test asks for the word. - **Items 1 to 5, first head, against `450279ca`:** 19 of 97 pasteable tests failed there, 33 of the 52 new cases passed there by design (a word `shlex.quote` quotes already, every `=` name in sh, dash and bash, the cases where both codecs agree, R6's and R8's rows, which kill mutants that base's code does not contain), and 97 of 97 passed at that head. - **Item 6, on #958's head, against the merged tree with the old line:** 20 failed, 377 passed in the three changed files. 15 new cases failed: five paste rows, the `-spec` row, the zsh row of the `=` test, six rows in `test_assembly_root_unreadable_paths.py`, the legacy-mode UTF-8 row and the euro row. 5 changed existing cases failed, since the line they assert changed. The helper `not_initialized` in #958's file takes the word. Each paste test empties the leg (`git submodule deinit`), runs bootstrap, checks the line whole, runs the printed command from the root in a shell, and checks the leg's `.git` is back at the pin. Git ignores a submodule path that begins with `-`, so `-spec` is checked another way: the command is exactly `git submodule update --init -- -spec`, and git's stderr names the path (without the `--` it prints its usage). - **Items 7 and 8, against the staged commit:** 11 failed, 405 passed: the `:spec` paste in sh, dash, bash and zsh (4), three text rows for a leading `:` (3), the default-mode lone surrogate (1) and three legacy-mode `printf` rows (3). 8 of the 19 new cases pass there by design (the rows for a space at either end, which kill `_paste_word(path.strip())` and are right in the old code). - **Pass at this head:** all of them. Each applied alone to the head's `bootstrap.py`, the changed test files run with `-x`. - **First head:** 30 mutants (R6, R8, four for item 1, seven for item 2, seven for item 3, and #944's ten), 30 killed. - **On #958's head, first round:** 38 killed, among them rv973's O3 (the comparison asked outside its `try`). - **Last round:** 45 applied, 45 killed: five for the `./` (none, `:/` only, always, also before `-`, glued in front of the word), R-H (`.strip()`) and R-F (`_paste_word(spelled(path))`), the command (`spelled(path)`, no `--`, `shlex.quote` only), six for Windows (the `nt` branch dropped, UTF-8 everywhere, `nt` inverted, `surrogatepass`, `os.fsencode` in the word, the read-back guard back), O3, R6, R8 and the rest of #961's and #944's. **One survived the first pass:** the `./` glued in front of the word. It is the same name to a shell, so no paste could tell; two text rows (`:my spec`, `:sp<0xff>ec`) kill it. All through `run9.sh` (the real `gh` unreachable, `EMAIL`, `GIT_SSH_COMMAND=false` and `GIT_CONFIG_GLOBAL=/dev/null` set), at the restacked head `d286b289`. | git | files | result | |---|---|---| | 2.43.0 | the pasteable file, `test_assembly_root_unreadable_paths.py`, `test_bootstrap_non_utf8_branch.py`, `test_bootstrap_after_bump.py`, `test_bootstrap_spelling.py`, `test_pinned_copy_text.py`, `test_adopt_reencoded_files.py` and the trio | 805 passed | | 2.47.1 | the same | 805 passed | Earlier heads of this branch also ran the other eight `test_bootstrap*.py` files (343 passed, 3 skipped for the reftable rows), and a predicted merge with main that added the casefold and create-twins files (662 passed). `git diff origin/main HEAD` touches five files: `templates/assembly-root/scripts/bootstrap.py`, `tests/test_assembly_root_unreadable_paths.py`, `tests/test_bootstrap_after_bump.py`, `tests/test_bootstrap_pasteable_lines.py` and `tests/test_bootstrap_spelling.py`. Their changed lines, in order, are the same as `git diff -U0 7e87e05 e23137c` gives (this PR's own commits on #958's head): 109, 54, 19, 483 and 25 lines, no difference. The three merges are: #958's final head `7c06c578` (clean), its squash `af0e60f2` (two conflicts, `bootstrap.py` and `tests/test_assembly_root_unreadable_paths.py`, both taken from this branch's side, which holds #958's content plus this change; the squash's tree equals the final head's) and main (already merged). - `_say_twin`, `fast_forward_to_pin`, `place_at_pin` and `create_at_pin`, which #948 (#896) edits. #948 has landed, with its `switch` line and its rename line through `_paste_word`, so they take this PR's leading-`=` quote with no change of their own (rv932's SHOULD 2). - A leg path that ends in a line feed keeps today's text, a deliberate exception to the Goal's "or the line says it cannot be pasted" (rv932's NIT 1). - Git ignores a `submodule.<name>.path` that begins with `-`, so no command can initialize a leg there; the printed command is right, and `validate-manifest.py` is not asked about such a path here. - Not checked: a real Windows host, Git Bash's argument conversion, macOS's `/bin/sh`, git 2.55 and Sonar's own analysis (cognitive complexity by hand: `_prints_as_it_is` 4, `_paste_word` 6, `_handed_to_git` 1, `_initialized` 4). A pinned copy: every project's `bootstrap.py` takes an `upstream-changed` row, and re-pins it. Review: rv973, lane 5's independent reviewer, not the writer (w961): READY at `e23137c7` (issuecomment-6282595318), after its READY at `2a2bc8f9` (issuecomment-6282590057) and at the first head `b2887cf5` (issuecomment-6253889729); `d286b289`, the head that lands, restacks that change unchanged onto #958's squash (lane 5's check from objects, issuecomment-6282600447). CI (Brett Heap's ruling 27): H0 is the head d286b28 itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base af0e60f; and the local gate passed on its predicted squash tree 993946c over main ee6f553: the assembly_root_unreadable_paths, bootstrap_after_bump, bootstrap_pasteable_lines, bootstrap_spelling, repo_hygiene, agents_md_codes, cli_reference sets, 635 passed. Landed on Brett Heap's word of 2026-10-18T14:45:28Z, relayed to every lane by lane 3 (#473 issuecomment-6251255596): "merge v1.2.0 PRs once reviewed and green". Closes #961 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #932 (comment)
The assembly root's pinned
bootstrap.pyprintedgit -C <leg> merge --ff-only <upstream>(move_back_to_pin) and an operation'sgit -C <leg> <verb> --continueand--abort(_say_in_progress) throughspelled(shlex.quote(...)).spelledwrites a byte that is not UTF-8 as #391's\udcXXtext, and a control character, a U+2028 and a bidirectional control as the escape Python spells it, andshreads that text as the characters it is. For an upstreamorigin/a+ byte 0xff +b, the line named no ref.The helper and its form
_paste_word(name)words ONE path or ref, local tobootstrap.py(it imports nothing new fromrepo_shape.py: a pinned copy must not rely on a newer shape copy, #370, #610).spelledhas nothing to escape and this stdout can write every character, it isspelled(shlex.quote(name))as it was. An ordinary name, a name with a space or an apostrophe and a printable name that is not ASCII (origin/café) are byte for byte what they were, so every line that was pasteable is unchanged."$(printf -- '<octal escapes>')", the bytesos.fsencodegives the name, each byte outside[A-Za-z0-9_./:@+=,-]a three-digit octal escape. Why this form: dash has no$'...'before 0.5.13 (Ubuntu's 0.5.12 reads$'a\377b'as$a\377b), and theprintfof dash, bash and zsh reads\NNN;--keeps a name that begins with-from being read as an option;%and the backslash and the apostrophe are escapes, so the format has no conversion and no quote to end; the double quotes keep the result one word. The word is ASCII, so it is the same on any stdout. It also covers a C1 control, a U+2028 and a bidirectional control in a name, and a name the stdout cannot write (PYTHONIOENCODING=asciiandorigin/café).spelled's text, as they were: one that ends in a line feed ($(...)strips it, so the word would name another path) and one whose bytes do not read back as the name (os.fsdecode). Git refuses a control character in a ref's name, so both can only be a leg's path, and a path with a final line feed is only a hand-made manifest's.The lines it changes
move_back_to_pin: the leg's path and the upstream of themerge --ff-onlyline (bootstrap.py:1041-1042)._say_in_progress: the leg's path of the--continueand--abortcommands (bootstrap.py:857).checkout_tracking_branch'sgit submodule update --init {spelled(path)}(slice D's lines), andfast_forward_to_pin,place_at_pinandcreate_at_pin(bootstrap.py: on a disk that folds case, a leg onMainwhose tracking branchmainis behind the pin is moved under its own HEAD, and bootstrap saysit is on main nowover an index that undoes the pin #896's).The 0xff probe
An upstream
origin/a+ byte 0xff +b,mainpast the pin with no commit of its own, the line pasted into/bin/sh -cin the root:Tests
tests/test_bootstrap_pasteable_lines.py, 45 tests. Each integration test runs the command from the printed line, as printed, through/bin/sh -cfrom the root (one of them also through dash, bash and zsh) and asks where the branch, or the merge, is afterwards, with the stdout lines asserted whole.-and a%(.gitmodulesholds the byte, andproject.yamlits\udcffescape);_say_in_progress's--continueand--abort, each run, in a leg at a path with the byte 0xff;origin/café, a CJK name, a space, a no-break space, the empty name,=x;PYTHONIOENCODING=asciiwithorigin/café: theprintfword;/bin/sh), the two names with no word, the legacy Windows code page, a stdout that is None, and no new import fromrepo_shape.Two existing tests pinned the old line and now pin the new one:
test_bootstrap_after_bump.py::test_the_branch_and_its_upstream_are_spelled_in_the_move_backand the byte row and theasciitest oftest_bootstrap_non_utf8_branch.py. The prose in the same line (the upstream as a person reads it) is still spelled.Fail on base (
50178305'sbootstrap.py): 41 of the 45 fail (collected 45, no skip). The four that pass by design are the printable-upstream line's three rows (ASCII, an apostrophe,café: the line must be what it was) and the no-new-import test.Pass at head: 45 passed (inside the runs below).
Runs
Load on this host made a full run impractical (a load of 85 to 270 on 32 cores), so the runs are scoped as the coordinator asked. One pytest process at a time,
run9s.shwithSHARDS=1, the realghunreachable.tests/test_bootstrap*.py(11 files, the new one included),test_pinned_copy_text.py,test_repo_hygiene.py,test_agents_md_codes.py,test_cli_reference.pytest_bootstrap_ref_store.py's reftable rows: this git makes no reftable repository)test_bootstrap_non_utf8_branch.py,test_repo_hygiene.py,test_agents_md_codes.py,test_cli_reference.pyorigin/main64464d31test_bootstrap_non_utf8_branch.py,test_repo_hygiene.py,test_agents_md_codes.py,test_cli_reference.py,test_pinned_copy_text.pyBefore the commit, on 2.43.0 with the tree uncommitted, the new file, eight other
test_bootstrap*files,test_leg_path_yaml_scalars.pyandtest_non_utf8_name_helpers.pypassed as well (the last two are the only ones of the list below that ran).Left to CI's
testsjob, which runs the whole suite on the head (59 files that mentionbootstrap.py): everytest_adopt_*that does (13),test_bom_reads, the fourtest_bump_leg*,test_doctor_*(5),test_family*(8),test_leg_path_yaml_scalars,test_legs_readers_not_a_list,test_load_yaml_refusals,test_non_utf8_name_helpers,test_park_resume_targets,test_push_branch_refspec,test_scaffold_*(5),test_setup_project_py,test_setup_sh,test_shape_doctor,test_siblings_family_name,test_template_text_placeholders,test_update_shape*(7),test_validate_family_branch_step,test_validate_pins_lockstep_lines,test_validate_workflow_gitmodules_urls,test_validator_child_encodingandtest_windows_paths. None of them reads a line this change moves: the lines are asserted intest_bootstrap_after_bump.py,test_bootstrap_non_utf8_branch.pyand the new file, all run.Mutants
Each applied alone to this head's
bootstrap.py, with the new file run (-x); every one is killed, none survives.spelled(shlex.quote(name))alwaystest_the_printf_word_prints_the_bytes_of_the_namebyte - 1)test_the_printf_word_prints_the_bytes_of_the_name:o, a digit after it is read into it)%printfbranchtest_a_name_with_nothing_to_escape_is_the_word_it_was_prints_as_it_isisstr.isprintable_prints_as_it_isis always truetest_the_printf_word_prints_the_bytes_of_the_name--afterprintf%is a bare byte%row/is not a bare bytetest_the_move_back_command_is_pasteable_whatever_the_upstream_is_named[byte-0xff]test_a_name_with_no_printf_word_keeps_the_text_it_hados.fsdecoderound-trip guard droppedtest_a_name_the_codec_would_not_read_back_keeps_the_text_it_had(it survived until that test was added)test_a_stdout_that_cannot_write_the_name_gets_the_printf_word_say_in_progresstest_the_commands_that_end_an_operation_are_pasteable_whatever_the_path[--continue]move_back_to_pin's pathtest_the_move_back_command_is_pasteable_whatever_the_legs_path_is_named[byte-0xff]move_back_to_pin's upstreamtest_the_move_back_command_is_pasteable_whatever_the_upstream_is_named[byte-0xff]Merge checks
git merge-tree --write-treeof this head, clean (exit 0, one tree id and no conflict line) againstorigin/main(64464d31, which moved from50178305while this was written; it touches the family root'sbootstrap.pyand three tests), againstfix/430-d-assembly-root-guardsand againstfix/896-casefold-branch-f(their local headsdf1bb186and80353dd1; neither branch is on origin yet, so these are an early look to be repeated when they are pushed).The overlap with #896
#896's head adds
_say_twin, whose way out iscommand = (f"git -C {spelled(shlex.quote(path))} switch " f"{spelled(shlex.quote(branch))}"). A branch can hold the byte 0xff past_argument_fault, so it has this defect. Whichever of the two lands second should make itcommand = f"git -C {_paste_word(path)} switch {_paste_word(branch)}". The helper is defined above_say_in_progress, so the order of the functions does not matter, and the two diffs touch different hunks.A pinned copy: every project's
bootstrap.pytakes anupstream-changedrow, and re-pins it.Closes #932
🤖 Generated with Claude Code
Summary by Sourcery
Make bootstrap's printed merge and in-progress operation commands reliably pasteable for unusual leg paths and upstream names.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: