Repository navigation
openRepoShape in Git Bash: the machine: row finds python, a drive-letter $OPENREPOSHAPE_REPO is a path, a Windows-spelled $OPENREPOSHAPE_BIN_DIR is respelled or refused, and tests-git-bash runs #598's installer cases (#649) - #656
Conversation
…there is no `python3`, a drive-letter `$OPENREPOSHAPE_REPO` is a path, a Windows-spelled `$OPENREPOSHAPE_BIN_DIR` is respelled with `cygpath -u` or refused, and `tests-git-bash` runs #598's installer cases, so Git Bash no longer says `python3 MISSING` beside a passing preflight, clones `https://github.com/C:/...` or prints an `export PATH` bash cannot use (#649) #596's Git Bash follow-ups, five lines, from #585's writer. - The `machine:` block `--install` ends with probed `python3` alone. Where Python came from python.org, Git Bash has `python` and no `python3`, so the row said `python3 MISSING` and pointed at `--preflight`, which then passed with `python`. It now asks `command -v` for `python3`, then `python`, in `find_python`'s order, and says `present, as python` when only `python` answers. The row keeps its name, so README's and the handbook's description of the block (`python3`, each `present` or `MISSING`) stays true. Still a probe and never a run: the Microsoft Store's `python3` stub reads as present, as macOS's Command Line Tools stubs already do, and `--preflight` runs the version check that tells a stub from a Python. Running that check here would break the block's promise to execute nothing but `gh auth status`, and the Store stub opens the Store when run bare. - `shape_clone_url` read `C:/src/openRepoShape` and `C:\src\openRepoShape` as an owner/name, so setup.sh and `--doctor` were handed `https://github.com/C:/src/openRepoShape.git`. A drive letter followed by `/` or `\` now goes through as a path, as `/...` and `....` do, and as the package's `clone_url` already reads it. No owner/name holds a colon. - `--install` named a Windows-spelled `$OPENREPOSHAPE_BIN_DIR` as typed, so its PATH note printed `export PATH="D:\tools\bin:$PATH"`, which the drive's colon splits, and never found the directory on a `$PATH` that holds `/d/tools/bin`. It is respelled with `cygpath -u` first, which Git Bash has, so every line names the directory as bash does. Where no cygpath answers, the value is refused before anything is placed, with the variable to set instead: no line about `$PATH` could work there. - `tests-git-bash` now runs `tests/test_install_sh_filter_repo.py` too: the uv install, the two readinesses and the pasted next steps, which README says Git Bash runs "tested in CI", ran on Linux only. actionlint 1.7.12 passes the workflow. - `test_a_ref_the_doctor_would_check_out_is_validated`'s docstring said the refusal arrives before any clone. The command clones first, then checks the ref before `git checkout` is handed it; the docstring now says so, and the order is unchanged. Tests: `test_the_machine_report_finds_a_python_by_either_name` (3 cases), `test_a_windows_spelled_bin_dir_is_spelled_the_way_bash_spells_it` (2), `test_a_windows_spelled_bin_dir_is_refused_where_no_cygpath_spells_it` (2) and `test_a_clone_is_handed_a_path_or_url_as_it_is` (7 spellings through setup.sh and through `--doctor`, 14). Eleven fail on the base, a524542. Off Windows a fake `cygpath` stands in for Git Bash's; on Windows the respelling test uses Git Bash's own on a real drive path. `install_with_machine` now names its directory with `bash_path`, because its `$PATH` holds no `cygpath`, and `run_cmd` takes a `cwd`. Closes #649 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-16T12:03Z) @codex review |
Reviewer's GuideThis PR adds Git Bash compatibility for Python discovery, drive-letter repository paths, and Windows-spelled installation directories, with safe cygpath-based normalization and refusal behavior. It also extends the Git Bash CI job to run the full installer coverage and adds focused regression tests for the launcher, setup/doctor clone inputs, installation paths, and command execution context. File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
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. |
|
Codex Review: Didn't find any major issues. Keep it up! 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". |
…E_BIN_DIR` is tested in both slashes and against a `cygpath` that answers a drive, so dropping the `D:/…` arm or the drive-answer refusal no longer leaves the suite green; three comments in openRepoShape and tests.yml say what is true (#649) Fix round 1 on PR #656, from rv649's review. - The respelling test and the refusal test now run over `D:\tools\bin` and `D:/tools/bin`. `fake_cygpath` takes the typed value, because it answers only the exact `-u -- <value>` it is asked. With the `D:/…` arm dropped the suite stayed green (91 passed), though `D:/tools/bin` with no `cygpath` printed `export PATH="D:/tools/bin:$PATH"`, the defect #649 names; five of the new cases fail under that mutation. - The refusal test has a third case: a `cygpath` that answers `D:/tools/bin` with exit 0, which the `[A-Za-z]:*` arm of the refusal refuses. With that arm dropped the suite was green too; two of the new cases fail under that mutation. - On Windows the refusal test's value is a drive path under `tmp_path` (`str(...)` for the backslash, `.as_posix()` for the slash), and the assert names that value. It was an absolute `D:\tools\bin`, so a refusal that regressed would have put a command on the machine's real D: drive, where the `iterdir` check could not see it. - Three comments are corrected; no code line or command changes. The `machine:` row's now names a cost it left out: a `python` that is Python 2, or older than 3.9, where there is no `python3`, reads as present and loses the `--preflight` pointer. The refusal's no longer says the bash running it does not know the drive letter as a drive: Git Bash does, and the drive's colon still splits `$PATH`. The `tests-git-bash` job's opening comment names the installer's second file. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
… it what to do where an earlier act left that branch, so `already exists` is no dead end (#607's sentence, in the two tools it left) (#657) #607 (#637) gave `family.py` and the holder's `validate-family.py` one sentence for a `switch -c` that fails `fatal: a branch named '<branch>' already exists` (exit 128): > If `git switch -c` says that branch already exists, an earlier act left it: give the branch another name, which starts it where the holder stands, or, only if that branch holds work not merged yet, the same command with `switch` in place of `switch -c` goes back to it and keeps every commit on it. Two tools still printed a `switch -c` and said nothing more. Both now say the same sentence under that line, each as its own constant, `BRANCH_LEFT`, as #637 does: - **`scripts/bump-leg.py`**, with "the project" where the others say "the holder". It follows the command in all three places `_switch_command` is named: - the `bump-leg-on-tracking-branch` remedy: after `and re-run.`; - the `bump-leg-root-detached` remedy: on the line under it; - the dry run's closing hint (#477): the new last line, under the hint. - **`setup-project.py`**: the same sentence, word for word (the holder), as one more comment line under the family block's `switch -c` line. The family block's two comment lines stay, and the line comes after them. No command line changes, and none is quoted differently. The sentence is words, not a second command, so no PowerShell quoting is added (#532) and no README sentence about quoting becomes false. Pinned files: none. This changes no file under `templates/`. 1. **Where the sentence goes in bump-leg's tracking-branch remedy: after `and re-run.`, not between the command and `and re-run.`** The remedy reads `Run:`, the command on its own line, `and re-run.` With the sentence between, `and re-run.` would hang under a full sentence. After it, the text before the sentence is byte for byte what it was, so the command line and `and re-run.` keep their places. The sentence is still under the `switch -c` line. 2. **setup's sentence is one comment line, unbroken (about 300 columns).** The brief asked for one comment line, word for word. `family.py`'s dry run prints it unbroken too. The block's other comments are hand-wrapped at 78 columns. A wrapped block would be a small change if the coordinator prefers it; the test would then join the comment lines. 3. **Every printed command line is unchanged**, and the tests prove it: bump-leg's line is read back by `shlex.split` and compared to the argv it always meant (`switch_argv`); setup's by `host_words`, on a folder with a space and one with a space beside an apostrophe. The block still has exactly two command lines. 4. **One rule, four copies.** #637's equality test, now `test_the_tools_say_the_same_words`, also asserts that `setup-project.py`'s constant equals `family.py`'s and the validator's, and that bump-leg's equals it with "the project" for "the holder". It was renamed from `test_the_two_tools_say_the_same_words`, because "two" is no longer true. The project's wording is pinned as text once, as `BRANCH_LEFT_PROJECT` in `tests/test_bump_leg_paste_lines.py`, beside the lines it follows; the holder's is #637's pinned copy in `tests/test_validate_family_branch_step.py`. `test_the_two_pinned_texts_differ_in_the_one_word_only` ties the two pinned copies together. 5. **Existing tests that pinned the old last lines take the sentence.** Seven tests in three files (listed under Tests). Each read the last line of an output, or the exact end of a remedy; none changes what it checks. 6. **The new module imports #637's typing helpers** (`leave_branch`, `refused_as_left`, `type_other_name`, `typed`, `without_c`, `head`, `on_branch`) instead of copying them, so the duplication gate sees one body. New module `tests/test_branch_left_bump_leg_setup.py`, 19 tests: - bump-leg, each of three places (`tracking`, `detached`, `dry-run`): the command is the one it always was and what stands under it is the sentence (3); the printed line makes the branch where none is left (3); where an earlier bump left `bump/spec-<id>` holding a commit of its own, the printed line fails `already exists` (exit 128) and changes nothing, then **another name** works (the new branch starts where the root stands, the bump goes through on it, its commit sits on the commit the root stood on, and `HEAD:spec` is the new commit) (3), and **going back** with `switch` for `switch -c` works (back on the branch at its own commit, the bump goes through there on top of it) (3). - setup, the family block: the line, the two comments, the sentence, the `add` line, two command lines (2, one per folder name); a real `setup-project.py` run prints the sentence once, under the right comment (1); in a real local holder (the `family` fixture's, on its tracking branch), with a real member: the printed line makes the branch where none is left, and where an earlier try left `family/add-<project>` with a commit of its own, **another name** works and `family.py add` goes through on it, and **going back** works and `add` goes through on top of the earlier commit (3). - `test_the_two_pinned_texts_differ_in_the_one_word_only` (1). Edited tests: seven cases in six test functions of the three bump-leg files, `test_bump_leg_dry_run_tracking.py` (4: the dry run's report, the difference from a dry run on a branch, the hint pasted for a root with a space, the real refusal byte for byte), `test_bump_leg_paste_lines.py` (1: the exact remedies) and `test_bump_leg_dry_run_truth.py` (2: the hint's branch name, `[local-remote-dir]` and `[origin]`); and, beside them, the extended `test_the_tools_say_the_same_words` in `test_family_switch_branch_exists.py`. Runs, on be1bff9 (BASE a524542 plus main 95780dd), through `run9.sh`, `gh` stub first on PATH: - New module with #637's module: `44 passed in 61.90s`. - Set 1, 21 files: every `tests/test_bump_leg*.py` (7), the new module, `test_bootstrap_after_bump`, `test_stdout_encoding`, `test_non_utf8_git_reads`, `test_agents_md_codes`, `test_commit_trailers`, `test_update_shape_paste_lines`, `test_validate_pins_lockstep_lines`, `test_pin_rewrite_layout`, `test_validator_child_encoding`, `test_validate_pins_unreadable_workflows_dir`, `test_template_text_placeholders`, `test_repo_hygiene`, `test_cli_reference`: `1040 passed, 2 skipped in 459.92s`. Both skips were there before: a case-ignoring disk (`test_bump_leg.py:693`) and a Windows default (`test_stdout_encoding.py:286`). - Set 2, 10 files: every file `git grep -l 'switch -c' tests/` names outside set 1 (`test_dry_run_asks_remotes`, `test_family_printed_quoting`, `test_family_refuse_up_front`, `test_family_switch_branch_exists`, `test_family_tracking_branch`, `test_validate_family_branch_step`, `test_validate_family_unnamed_mounts`), plus `test_setup_by_hand_commands`, `test_setup_project_py`, `test_setup_sh`: `592 passed in 506.74s`. - Before #637 landed, on 6ac545f: set 1 `1042 passed, 2 skipped`, set 2 (9 files) `441 passed`. **Fails on BASE.** The same five test files from the head, run on main 95780dd (no source change): `26 failed, 80 passed in 168.33s`. 18 of the 19 new tests fail, `test_the_tools_say_the_same_words` fails (no `BRANCH_LEFT` in either new place), and the seven edited tests fail. The one new test that passes on BASE is `test_the_two_pinned_texts_differ_in_the_one_word_only`: it reads text only. On the first commit alone, against a524542, the old module copy gave `20 failed, 1 passed`. A real project (`scaffold-project.py` into local remotes, a recursive clone, the spec leg advanced on a branch of its remote) and a real holder (`family.py init`, then a real `setup-project.py Sample --yes --family Orbit` run). An earlier act left the branch with a commit of its own. - **On BASE a524542:** `bump-leg.py` on the tracking branch ends `Run:` / the `switch -c` line / `and re-run.` and nothing more. The line typed as printed gives `fatal: a branch named 'bump/spec-712e4ce5d766' already exists`, exit 128. The detached refusal and the dry run's hint end on the command the same way. setup's block is the `switch -c`, `# a branch first: add refuses the` and `# holder's tracking branch`, then the `add`. Typed, `fatal: a branch named 'family/add-sample' already exists`, exit 128. - **On the head:** each of the four prints the sentence under the line, and the same line gives the same 128. Then: - another name (`<line>-2`): exit 0; `bump-leg.py` goes through on it, on top of the commit the root stood on; `family.py add` goes through on it; - going back (`switch` for `switch -c`): exit 0, at the earlier commit; `bump-leg.py` and `family.py add` go through, their commit on top of it. - Both choices already worked on BASE: the sentence tells a person so. No row needs real GitHub. - **A real earlier bump, not a made-up branch.** I ran a real bump on `bump/spec-<id>`, then went back to `main`. There the root shows ` M spec` (the leg is still at the bump's commit), and the next `bump-leg.py` is refused `bump-leg-root-dirty` before it reaches the `switch -c` line. After `git submodule update` puts the leg back, the line appears, fails `already exists`, and the other name works. See the checklist candidates. Each hunk was reverted or broken alone in a scratch clone of the head, and the new module plus `test_the_tools_say_the_same_words` was run. Every mutation makes new tests fail: | Mutation | Result | |---|---| | bump-leg `BRANCH_LEFT` says "the holder" | 13 failed: the equality test, and the three places in each of four tests | | bump-leg `BRANCH_LEFT` drops `only if` | 13 failed, the same | | `bump-leg-root-detached` remedy without the sentence | 4 failed (the `detached` case of each of four tests) | | `bump-leg-on-tracking-branch` remedy without the sentence | 4 failed (the `tracking` case of each) | | the same remedy with the sentence above `and re-run.` | 4 failed, the same | | dry run's hint without the sentence | 4 failed (the `dry-run` case of each) | | `_switch_command` prints `switch -C` | 9 failed (the "another name" and "going back" cases, and the three "command is the one it was" cases); the control, where no branch is left, still passes, as `-C` makes a new branch too | | setup `BRANCH_LEFT` says "the project" | 7 failed (the equality test and six) | | setup block without the sentence | 6 failed | | setup sentence above the block's two comments | 6 failed | | setup `BRANCH_LEFT` drops `only if` | 7 failed | The other hunks change only docstrings and comments (`_switch_command`, `_branch_to_bump_from`, `family_next_command`, the comment above the dry run's hint). They change no behaviour. | Ref | Result | |---|---| | origin/main d4a7fab (one commit past my merge of 95780dd; touches `scripts/family.py`, `scaffold-project.py`, `docs/cli.md`) | clean | | #637 (landed as 95780dd) | merged into this branch once, clean | | #623 (lane 2, `setup-project.py` steps (3) and (4); landed as abb956e) | merged in with #637, clean: its hunks are far from `family_next_command` | | #639 08b6245 (lane 5, #611) | clean | | #641 e87fcb0 (touches `bump-leg.py` and `family.py`) | conflicts in `scripts/family.py`, and the same conflict arises against main alone: not from this branch. `bump-leg.py` merges clean. | | #615 6ad2303 (touches `bump-leg.py`) | clean | | #631, #636 (lane 4) | clean | | #642, #643, #644 | clean | | lane-5 siblings fix/645 e2110f8, fix/646 2216e1c, fix/648 852c5c4, fix/649 d91b314 | clean | | #561 (lane 4's `fix/bump-leg-tag-dry-run`) | its work is on main already (7d9bec9, `asked = commit`); its stale pull head conflicts with main alone, so it is not an open PR to check | - No README, AGENTS.md or handbook edit. I checked every sentence about these lines: AGENTS.md:337-351 and 374 (the dry run "prints the `git switch -c`"), README.md:157 and 1011, `docs/cli.md` (generated; no `--help` text changes) and the handbook. None becomes false. - Windows and macOS are not checked here, and no CI job checks them since #597. The tests type argvs, run no shell, compare no file that `write_text` wrote, and read setup's lines with `host_words`. - A branch checked out in another worktree makes `git switch <branch>` fail (#637's "Left out", N1); the sentence is not made longer for it. - I did not tick or mark the lines on #458 and #459. - **Review rv647 (APPROVE WITH NITS, 0 blockers, 0 should-fix, 5 nits), and what is left of it:** - Commit 6ac545f's message says "Four existing tests … take the sentence". It is six test functions (seven cases) in three files, as the Tests section says. The squash message is this body. - `scripts/bump-leg.py`'s comment near :1164 still says "the three tools follow one rule"; there are four copies of the sentence now, and `setup-project.py`'s comment names two. Left for the `bump-leg.py` issue filed from #458's lines, which lands after this and edits that file. - No AST guard holds a fourth `_switch_command` caller to the sentence, as #637's `test_every_remedy_that_names_a_switch_line_ends_with_the_sentence` holds the validator's. - Setup's sentence prints as one comment line of about 317 columns in a block wrapped at 78. It is unbroken as `family.py`'s `_branch_said` prints it, which is what lets the byte-for-byte equality tests hold (decision 1). - **bump-leg.py: after a real bump on a branch and a switch back to an older branch, the root is dirty (` M spec`) and `bump-leg-root-dirty`'s remedy, "commit or stash them first", is wrong advice.** The leg is left at the bump's commit and `git switch` does not move it (README says the leg is left detached at the new commit). `git submodule update` (or `make bootstrap`) puts it back and clears the refusal. The remedy could say so where the one dirty path is a leg's mount. It also means the `switch -c` sentence is reached only after that step in this flow. - **The same flow's "go back" choice meets that refusal too** (rv647): `git switch` to the branch that holds the earlier bump leaves the leg behind, so the re-run is refused `bump-leg-root-dirty`; after `git submodule update` it says `nothing to do`, and only the push is left. - **update-shape.py:5279 prints `NEXT git -C <root> checkout -b shape/update-<12> && git … commit`** (rv647, read only): it fails `already exists` the same way, and the `&&` chain then commits nothing. It is `checkout -b`, outside #647. - **family.py, after a real earlier `add` and a switch back:** another name works for the switch, but `family.py add` is then refused `member-path-taken`, because git left the populated `members/<Project>` checkout behind (rv647; identical on the base). It bears on #637's "another name works". Review: rv647 found 0 blockers, 0 should-fix and 5 nits on be1bff9 (APPROVE WITH NITS); nits 1 and 2 are answered in this body, nit 3 (the stale "three tools" comment in `scripts/bump-leg.py`) rides with #672, and nits 4 and 5 are noted above. CI (Brett Heap's ruling 27, with his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 be1bff9 passed tests (Linux), tests-git-bash and SonarCloud; the head ac8a810 is a clean update-branch merge of main 8062810, with SonarCloud, tests-git-bash and package green on it, and the local gate passed on the head itself: the bump-leg set, 1040 passed (2 skipped), and the `switch -c` and setup set, 592 passed. Landed on Brett Heap's word of 2026-10-16T13:07:34Z, "merge #656 and #657 when green". Closes #647 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 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 6 hours and 23 minutes by commenting |
(c6122e8) under #646's move back (#646 x #611) No conflict. main moved from a524542 to 35133f2 (#623, #637, #622, #620, #628, #639, #621, #627, #657, #656). Of those, #637, #622 and #639 change scripts/family.py, in hunks git merged on its own; none changes this branch's three test files. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained #628 (#600), #639 (#611), #621 (#602), #627 (#606, lane 2's README and handbook), #657 (#647), #656 (#649) and #698. README.md, adopt-project.py and every other file merged as text; docs/handbook.html conflicted at eight marker hunks and nowhere else: - the seven `Starting a project in a new organisation` markers, and the `The three legs` markers in the masthead, `ci` and `legs` that sit in the same hunks: this branch and #627 each moved them for their own README edits; - the one `Adopting an existing repository` marker. Every hunk took this branch's side, and then every marker was recomputed from the merged README.md with tests/test_repo_hygiene.py's `readme_segments` and `readme_fingerprint`: Starting (7) 45a3f886, The three legs (3) 1d9f5bf9, Adopting (1) a7e9e29f. The page's text holds both sides' edits: #627's handbook changes merged as text beside this branch's, so each moved marker reads a section whose page text was amended by the pull request that changed it. Caps: README.md is 1473 lines, its cap: 1422 + 2 (460) + 5 (550) + 12 (563) + 22 (564) + 10 (606). AGENTS.md is 494, its cap. test_repo_hygiene.py and test_readme_sections.py pass (154). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…KEEPS ITS NAME (which `--project` must be), ITS IDENTITY AND ITS FULL HISTORY, and the notes are current to main de92614, with seven more pull requests, lane 2's re-run-`plan` line, lanes 4's and 1's known limits, Developer Mode for a Git Bash symlink and the trusted publishers (#563, L4-47) - L4-47 (#457, lane 4 on #630 at 14:13:32Z): README.md:771-772 and docs/handbook.html:1276-1277 say "it KEEPS ITS NAME (which `--project` must be), ITS IDENTITY AND ITS FULL HISTORY, and becomes the assembly root". Two lines each, no line added: README.md is still 1473, its cap. The § Adopting marker moves to b23e9f98, text and marker; no other marker moves. - The notes, "What changed since v1.0.1": #628 (#600), with lane 2's line for a plan written before v1.1.0 (#473, 12:53:40Z) after it, and #621 (#602) under adopt-project.py; #657 (#647) under update-shape.py, bump-leg.py and a project's own scripts; #639 (#611) under scaffold, setup, family and the shared readers; #656 (#649), #698 (#679) and #627 (#606) under install, CI and the docs. Every pull request since bfed143 is cited (`git log --first-parent bfed143..de92614`, 74). - Known limits: lane openRepoShape-4's ten lines (#630 issuecomment-6217406337: L4-05, -06, -07, -08, -17, -43, -45, -54, -24, -156) and lane openRepoShape-1's twenty-six D9 lines (#457 issuecomment-6214768828), as two lists, one sentence each; and the Git Bash limit says that Windows lets a non-administrator make a symlink only in Developer Mode, as #596 does. - How this release is cut: the trusted publishers are filled from Brett Heap's 13:04:43Z ("the pypi are added") and 13:07:56Z ("they match what you listed"). - The counts (74 pull requests; 83 of 135 milestone issues closed at 17:16Z), the "current to main" sha and the open list (52) are at main de92614, still placeholders for tag time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…IR` is respelled or refused before install.sh's fetch, and the PATH note quotes the directory it prints and refuses one holding `:` or a newline (#667) (#726) #649's two leftovers (lane 5's checklist lines on #461). One commit, **a3cde999**, on main 35133f2 (#656 is in it). It changes `install.sh`, `openRepoShape` and the two suites `tests-git-bash` runs, `tests/test_install_sh.py` and `tests/test_openreposhape_command.py`, and nothing else. `git show --numstat`: `install.sh` +32/-4, `openRepoShape` +57/-1, `test_install_sh.py` +155/-2, `test_openreposhape_command.py` +216/-13. README, the handbook and `docs/cli.md` are unchanged: every sentence about `--install`, the PATH note and `$OPENREPOSHAPE_BIN_DIR` stays true (`export PATH=…` is still what they say). **1. install.sh applies #656's rule where it reads the variable** (`main`, before the symlink check and the fetch). - Before: `target="$bin_dir/openRepoShape"` was built from the raw value. With `D:\tools\bin`, the `preflight:` line and the four `done:` lines (and `install:` for an old release) said `D:\tools\bin/openRepoShape`, while the command's own lines said `/d/tools/bin/openRepoShape`. With no `cygpath`, the command's `--install` refused (exit 2) after install.sh had fetched it, and install.sh said only "the <version> openRepoShape's --install refused (exit 2)". - Now: a value that starts `[A-Za-z]:/` or `[A-Za-z]:\` is spelled with `cygpath -u --`, so every line names one directory. Where no `cygpath` answers, fails, or answers a drive spelling again, install.sh refuses with exit 2 before the symlink check and before any request. The `case` is a copy of `install_commands`'s, because install.sh is downloaded alone. - The refusal does not echo the value. A value that holds a newline would forge a line. **2. The PATH note quotes the directory it prints** (`export_path_line`, new, in `openRepoShape`). - Before: `export PATH="$dir:$PATH"`, the directory as it was between double quotes. For `a"b$HOME c` it printed `export PATH="…/a"b$HOME c:$PATH"`, which `eval` rejects (`unexpected EOF while looking for matching `"``). For ``a`echo X` `` it printed a line that RUNS `echo X` when pasted, and for `a$b` one that drops `$b`. - Now, for a directory holding a `"`, `$`, backquote, `\` or `!`, the line is `export PATH='<dir>':"$PATH"`, with `'\''` for an apostrophe. Every other directory keeps today's line byte for byte, a space or an apostrophe included (both are safe in double quotes). - `!` is in the set because an interactive bash reads it inside double quotes as history: `echo "a!b"` answers `bash: !b: event not found` at a prompt, and a person pastes this line at a prompt. A profile file never does. - Where it is called: `say " $(export_path_line "$dir")"`, in place of the old inline line. **3. `--install` refuses a directory holding `:` or a newline** (`install_commands`, beside #656's early checks), before anything is placed, with exit 2 and the remedy. - A `:` splits a `$PATH` whatever the quoting, and a newline breaks the printed line in two, so no line about `$PATH` can name them. - The newline check runs first, before the Windows spelling, and its message does not name the value, so no later refusal can echo a forged line. The colon check runs after the Windows spelling: a drive spelling holds a `:` by definition, is respelled without one, and refuses what it cannot respell. A `cygpath` answer that holds a `:` is refused there too. - The directory may come from `$HOME/.local/bin`; the colon refusal names it. - **install.sh respells and refuses only where respelling is impossible** (the issue offered "respell, or refuse"). It is #656's decision for the command, and install.sh must not answer one value differently: a test feeds each value through both (below). - **install.sh gets the drive-letter rule only.** The brief and the issue's first line name that rule. The `:` and newline refusals are the command's (issue line 2). Where install.sh is handed such a value, the command refuses after the fetch, as it always did. Named below as a checklist candidate. - **The line's shape is `export PATH='<dir>':"$PATH"`.** The directory is the only quoted part. The probe `a"b$HOME c` now prints `export PATH='…/a"b$HOME c':"$PATH"`, and `eval` of it sets `$PATH` to exactly the directory plus the old `$PATH`. - **Any backslash is quoted,** though `a\b` survives double quotes (`\b` is not special there). The issue lists `\`, and one rule is easier to hold than "a backslash before one of four characters". - **The apostrophes are walked, not replaced with `${dir//…}`.** How a pattern substitution reads a quote in its replacement has changed between bash releases, this runs on 3.2, and `sed` is not one of the programs `--install` uses (`INSTALL_NEEDS`). I could not run bash 3.2 here (5.2.21 only); the loop uses `${x%%"$q"*}` and `${x#*"$q"}`, which are long-standing 3.2 forms. - **`BIN_DIR_SPELLINGS` gained `lower-case`** (`d:\tools\bin`). #656's two tests now run over it too (+5 cases, 15 in all, all passing on both the base and the head), so `[A-Za-z]` is pinned in the command as well as in the copy. New: 56 cases. In `tests/test_openreposhape_command.py` (39 with #656's two extended): the PATH note's lines for 12 names, `export_path_line` alone for the same 12 (so a `"` and a `\` are held on Windows, where no file can be named with one), 3 through `bash -i`, 5 refusals (a colon, two directories, a drive-relative name, a newline, a drive spelling holding a newline), the `$HOME` default, a `cygpath` answer holding a colon, and +5 from the `lower-case` spelling. In `tests/test_install_sh.py` (17): one value through install.sh and the command, in 3 spellings × 4 `cygpath` states (12; where it spells, both place the command at the same directory and name no other; where it does not, both refuse with exit 2 and install.sh made no request); every `preflight:`, `install:` and `done:` line with the directory respelled, over the four endings (4); and the symlink check seeing the respelled directory (1). - **On the base 35133f2** (the head's two test files over the base's code): `47 failed, 19 passed` in the new selection. The 19 are #656's own cases and the plain, space and apostrophe names, which keep today's line. - **On the head a3cde99, dirty=0:** - `tests/test_openreposhape_command.py tests/test_install_sh.py tests/test_install_sh_filter_repo.py tests/test_openreposhape_package.py`: **276 passed** in 589.67s (the machine's load average was 115). - `tests/test_repo_hygiene.py tests/test_workflow_concurrency.py tests/test_release_workflow.py tests/test_readme_sections.py tests/test_cli_reference.py tests/test_setup_by_hand_commands.py tests/test_readme_windows_quoting_sentence.py tests/test_gh_guard.py`: **250 passed** in 175.17s. - **The three files `tests-git-bash` runs, with Windows' `write_text` emulated** (a plugin makes every `Path.write_text` write CRLF): **218 passed** in 326.60s. A probe test takes the plugin's effect: 1 passed with it, 1 failed without. - `actionlint`: not run, no workflow is touched (`tests.yml` is as on main). Each row reverts one arm, refusal or hunk in a throwaway clone and runs the new cases and #656's (66 selected). The first column is how many failed. | Mutation | Failed | Where | |---|---|---| | install.sh: the whole drive block off | 17 | parity (12), line cases (4), symlink | | install.sh: no backslash arm | 13 | parity `backslash` and `lower-case`, line cases, symlink | | install.sh: no slash arm | 4 | parity `slash` | | install.sh: `[A-Z]` for `[A-Za-z]` | 4 | parity `lower-case` | | install.sh: no `cygpath` call | 8 | parity `spells` (3), line cases, symlink | | install.sh: no refusal | 9 | parity, every state but `spells` | | install.sh: no drive-answer refusal | 3 | parity `again` | | install.sh: no `|| spelled=""` guard | 3 | parity `fails` (set -e exits silently) | | install.sh: `cygpath -u` without `--` | 8 | parity `spells`, line cases, symlink | | install.sh: the block moved after the symlink check | 8 | line cases, symlink, parity `spells` | | install.sh: the block moved after the release lookup | 17 | parity (12: a request was made, or the target was named raw), line cases (4), symlink | | install.sh: the refusal echoes the value | 9 | parity refusals (typed value in stderr) | | command: no newline refusal | 2 | `a-newline`, `a-drive-spelling-and-a-newline` | | command: newline checked after the drive arm | 1 | `a-drive-spelling-and-a-newline` (the value would be echoed) | | command: no colon refusal | 5 | colon (3), `$HOME` default, respelled answer | | command: colon checked before the drive arm | 31 | #656's cases, parity, respelled answer | | command: `"` not quoted | 2 | `a-double-quote`, both tests | | command: `$` not quoted | 5 | `a-dollar`, `an-apostrophe-and-a-dollar`, both tests, `bash -i` | | command: backquote not quoted | 2 | `a-backquote`, both tests | | command: `\` not quoted | 4 | `a-backslash`, `a-trailing-backslash`, both tests | | command: `!` not quoted | 5 | `a-bang`, `two-apostrophes-and-a-bang`, both tests, `bash -i` | | command: apostrophes not walked | 4 | the two apostrophe-with-special names, both tests | | command: an apostrophe alone forces single quotes | 2 | `an-apostrophe`, both tests (today's line kept) | | command: always single quotes | 9 | the plain, space and apostrophe names in both tests (6), and #656's `not-on-path` cases (3) | | command: the old inline line at the call site | 11 | the 9 e2e cases with a special character, 2 of `bash -i` | All 25 mutations are killed. A first version of "an apostrophe alone forces single quotes" was a syntax error, so every case failed; it was rewritten with `\'` and re-run, and the row is that run. On the base (35133f2 code) and on the head: - **install.sh, `D:\tools\bin`, a `cygpath` that spells it:** base lines `preflight: D:\tools\bin/openRepoShape --preflight` and `done: … installed at D:\tools\bin/openRepoShape, but the preflight did not run to the end (exit 127)` (the exit 127 is Linux: the drive spelling resolves only in Git Bash). Head: `preflight: <dir>/openRepoShape --preflight` and `done: … installed at <dir>/openRepoShape, pinned to the release tag, and this machine is ready`, exit 0. - **install.sh, no `cygpath`:** base exit 2 after 2 requests (the redirect and the fetch), with the command's refusal. Head: exit 2, 0 requests, install.sh's own refusal. - **The PATH note:** base `a"b$HOME c` → `eval` fails with `unexpected EOF`; `a$b` → `$PATH` gets `…/a`; ``a`echo X` `` → `$PATH` gets `…/aX` (the backquote ran). Head: all three give exactly the directory plus the old `$PATH`. A backslash, a bang, an apostrophe, a space and a plain name keep or gain the right line (the first two single-quoted, the last three today's line). - **A directory holding `:` or a newline:** base exit 0 and the command placed under it, with a line `eval` cannot use. Head exit 2, nothing placed. - **Needs Windows to be real:** `cygpath -u` on a real drive path, a real `D:`. Not faked as real; the PR's `tests-git-bash` run checks it through the tests (below). - origin/main de92614 (adds #698, `tests/test_doctor_next_is_a_command.py` only): clean. - #630 `l3-563-docs` 5b140bd: conflicts in `docs/handbook.html`, and the same single file conflicts between #630 and main de92614 alone (#630 is lane 3's to refresh). #630 touches none of this PR's four files, so my change adds no region. - Lane 3's `l3-668`, `l3-669`, `l3-677`, `l3-678`; lane 4's `fix/610`, `fix/645`, `fix/646`, `fix/648`, `fix/650`, `fix/651`, `fix/653`, `fix/655`, `fix/658`, `fix/659`, `fix/664`, `fix/696`: clean, and none touches `install.sh`, `openRepoShape` or the five installer and launcher suites. - Checked on Linux: CRLF `write_text` (218 passed). No new assert compares a path bash printed with a Python path (the PATH-note line is built from `bash_path(tmp_path)`, as #656's tests are). - Not checked: on `windows-latest`, Git Bash's real `cygpath` for the new install.sh cases (3 spellings, through `put_cygpath`, which puts a `#!/bin/sh` wrapper around `shutil.which("cygpath")` on that `$PATH`, as `put_on_path` does for `mktemp`); the 12 PATH-note names that can be created there (`a$b`, a backquote, `!`, a space, an apostrophe); the `bash -s` runs that carry the script as bytes and the directory in the environment; and a newline or a `:` in `$OPENREPOSHAPE_BIN_DIR` crossing the environment into Git Bash. The names with `"` or `\`, the `bash -i` cases, the `$HOME` default and the respelled-colon case skip on Windows; `export_path_line` is still run for the `"` and `\` names there, by environment, not by a file name. If one fails there, its log shows the typed value and the printed line. - bash 3.2 (macOS's `/bin/bash`) was not available: only the loop's forms are claimed for it. - **install.sh's own `:` and newline refusals.** See candidate 1. - **Release notes.** `--install` now refuses a `:` or newline in the directory, where it used to place the command and print an unusable line: a visible change for `docs/release-notes/v1.1.0.md` (lane 3's #630). Not edited here. - README, the handbook and `docs/cli.md`: no sentence became false. 1. **install.sh learns of a `:` or a newline in `$OPENREPOSHAPE_BIN_DIR` only after its fetch.** Handed such a value, install.sh fetches the launcher, and the command refuses with exit 2; install.sh says "the <version> openRepoShape's --install refused (exit 2); its own refusal above says why". A release older than this change does not refuse at all. Copying the two `case` arms into `main`, before the symlink check, would make install.sh refuse first and name the variable, as it now does for the drive spelling. 2. **A directory holding another control character** (a carriage return, an escape) is still echoed as typed by the refusals and by the note's `is not on $PATH` line; only the newline is refused first. It cannot forge a line, but an escape sequence reaches the terminal. Review: rv667, lane 5's independent review, approved a3cde99 with nits, and codex reviewed it. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 a3cde99 passed tests, tests-git-bash and SonarCloud; the head a22b94c is a clean update-branch merge of main bf1f099, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 7e21cd2 over main dc7d3fb: the install_sh, openreposhape_command, install_sh_filter_repo, openreposhape_package, repo_hygiene, agents_md_codes, cli_reference sets, 422 passed. Landed on Brett Heap's word of 2026-10-17T10:59:39Z, relayed by lane 3 on #473: "merge lane 5's approved v1.1.0 drafts". Closes #667 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #649 (comment)
#596's Git Bash follow-ups, the five lines of #649. Two commits on main a524542: d91b314, the fix, and e85573a, fix round 1 from rv649's review. Together they change
openRepoShape,.github/workflows/tests.ymlandtests/test_openreposhape_command.py, and nothing else.git diff --numstat, d91b314:openRepoShape+42/-4,tests.yml+6/-1, the test file +182/-7. e85573a: +9/-6 (comments only), +3/-2 (a comment only), +56/-27. README, the handbook anddocs/cli.mdare unchanged: every sentence about these blocks stays true (below).What changed and why
Fix round 1 (rv649)
rv649 found the launcher's code correct and asked for changes: 0 blockers, 1 should, 6 nits. e85573a answers each point. It changes no line of the launcher's code and no command of the workflow: three comments and the tests.
D:/…arm had no test. Both BIN_DIR tests now run overD:\tools\binandD:/tools/bin, andfake_cygpathtakes the typed value. On Windows they usestr(...)and.as_posix()of a path undertmp_path. With the arm dropped the suite stayed green (91 passed); now 5 cases fail.cygpaththat answersD:/tools/binwith exit 0. With that arm dropped the suite stayed green; now 2 cases fail.pythonthat is Python 2, or older than 3.9, reads as present. Named as a cost in decision 1 and in the comment abovemachine_report. No code change:--preflightstill refuses those machines.cygpathoffPATH. Git Bash does readD:\…as a drive. The comment ininstall_commandsand decision 3 now give the true reason.D:drive path. It is now a path undertmp_path, and the assert names that value, so a refusal that regressed would place the command where the test looks.C:\src\…gavehttps://github.com/C:\src\openRepoShape.git), the Store stub clause (dropped, see decision 1) and the report's diffstat totals (the exact--numstatcounts are at the top). d91b314's own commit message keeps its....typo and its one-URL sentence in history; the squash message is this body.pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 no longer conflicts.The fix
1. The
machine:row for Python (machine_report).command -v python3and nothing else. Where Python came from python.org, Git Bash haspythonand nopython3, so the row saidpython3 MISSINGand printed the line pointing at--preflight, which then passed withpython.python3, thenpython, infind_python's order. When onlypythonanswers, the row sayspython3 present, as python, and nothing points at--preflightfor it. With neither, it saysMISSING, as before. Withpython3, it sayspresent, byte for byte as before.2. A drive-letter
$OPENREPOSHAPE_REPO(shape_clone_url).C:/src/openRepoShapeandC:\src\openRepoShapewere read as an owner/name, so setup.sh's clone and--doctor's were handedhttps://github.com/C:/src/openRepoShape.gitfor the first andhttps://github.com/C:\src\openRepoShape.gitfor the second./or\is a path, and goes through as it is, as/…and.…do. No owner/name holds a colon. The package's ownclone_url(src/openreposhape/standard.py,DRIVE_RE) already read a drive letter this way.3. A Windows-spelled
$OPENREPOSHAPE_BIN_DIR(install_commands).--installnamed the directory as typed. WithD:\tools\binit printedinstalled at D:\tools\bin/openRepoShapeandexport PATH="D:\tools\bin:$PATH", which the drive's colon splits in two. It also never found the directory on a$PATHthat holds/d/tools/bin, so it printed that line every time.cygpath -u, which Git Bash has, before anything is checked or placed. Every line then names/d/tools/bin, and the PATH note appears only when that directory is not on$PATH.cygpathanswers (none on PATH, it fails, or it answers a drive spelling again), the value is refused before anything is placed: "--install would place this command in 'D:\tools\bin', which is spelled for Windows, and no cygpath here spells it the way bash does, so the directory could not go on $PATH: the drive's colon splits it. Nothing was placed. Set $OPENREPOSHAPE_BIN_DIR to the directory as this shell spells it, then run this again."4.
tests-git-bashrunstests/test_install_sh_filter_repo.py. That file holds #598's uv install, the two readinesses and the pasted next steps, which ran on Linux only, while README says Git Bash runs step 3's one line "tested in CI". It is the third path on the job's pytest line, with a comment saying the installer's suite is two files. The job's other lines, the header's "the launcher's and the installer's suites, and nothing else" and #630's release-notes sentence of the same meaning stay true.5. The stale docstring.
test_a_ref_the_doctor_would_check_out_is_validatedsaid the refusal "has to arrive before any clone does". The command clones first, then checks the ref, and refuses beforegit checkoutis handed it. The docstring now says that. The order is unchanged.Decisions and their evidence
command -vfor both names, and no version check.gh auth status, andtest_the_machine_report_runs_no_installerholds it to that: every fake on its PATH logs a run. Runningfind_python's check would break that promise.python3stub reads as present, as macOS's Command Line Tools stubs forpython3andgitalready do (setup.sh's comment at :41-47). That is not new: the base askedcommand -v python3too, and the Store installs itspython.exeandpython3.exealiases together, sopython3answers first.pythonthat is Python 2, or older than 3.9, where there is nopython3. On the base such a machine saidpython3 MISSINGand printed the--preflightpointer. Now it sayspresent, as pythonand prints no pointer. It is rare (CentOS 7, macOS 10.14 or older, Python 2.7 from python.org), and--preflightstill refuses it with "no Python 3.9 or newer on this machine (tried python3, python)". The comment abovemachine_reportsays so.python3, and the state carries the name that answered, in the formgh's row already uses (present, logged in). So README's sentence ("git,ghand whether it is logged in,python3, andgit-filter-repo… eachpresentorMISSING") and the handbook's stay true, and README is not edited. Labelling the rowpythonwould have made both false in Git Bash.cp,mkdirand the record took the Windows spelling already, and only the printed lines were wrong. Where nocygpathanswers, nothing in the command can spell the value as bash's$PATHneeds it: in Git Bash withcygpathoffPATHthe drive is real but its colon still splits$PATH, and on LinuxD:\tools\binis a relative directory with a colon in its name. No$PATHline could work there, so it refuses. Acygpaththat answers a drive spelling again is refused alike.install_with_machinenow names its directory withbash_path. Its$PATHholds only the programs--installruns, and nocygpath. On Windows the oldstr(bin_dir)is a drive path, which would now be refused. Off Windows,bash_pathchanges nothing. Other tests that passstr(...)keep the full PATH, so on Windows they now go through Git Bash's owncygpath.run_cmdtakes acwd. On the base, and under a mutation, the new BIN_DIR tests'D:\tools\binis a relative directory. Withcwd=tmp_pathit lands in the test's own directory, never in the checkout.BIN_DIR_SPELLINGS,typed_bin_dir). Off Windows the typed value is the fixedD:\tools\binorD:/tools/bin. On Windows it is a path undertmp_path, so neither test can place a command on the machine's ownD:drive.Tests
New, in
tests/test_openreposhape_command.py, 27 cases:test_the_machine_report_finds_a_python_by_either_name:pythonalone, both, neither (3).test_a_windows_spelled_bin_dir_is_spelled_the_way_bash_spells_it: not on$PATH, on$PATH, each in both slashes (4). On Windows it is a real drive path (str(bin_dir)or itsas_posix()) and Git Bash's owncygpath; elsewhere a fakecygpathanswers as that one would.test_a_windows_spelled_bin_dir_is_refused_where_no_cygpath_spells_it: nocygpath, acygpaththat fails, acygpaththat answersD:/tools/bin, each in both slashes (6), on the machine report's own$PATH.test_a_clone_is_handed_a_path_or_url_as_it_is: owner/name, URL, scp, POSIX path,C:/…,C:\…andd:\…, each through setup.sh (a setup.sh that prints$OPENREPOSHAPE_REPO) and through--doctor(agitthat prints the URL it is handed and fails) (14). Nothing is cloned or fetched.On the base a524542 (this head's test file over the base's command): 17 failed, 80 passed in 62.32s. The failures are exactly the new cases that need the fix:
python-alone, the four respelling cases, the six refusal cases and the six drive-letter cases. The other 10 new cases pass there too, as they should.On the head e85573a (Linux,
dirty=0):tests/test_openreposhape_command.py,tests/test_install_sh.py,tests/test_install_sh_filter_repo.py,tests/test_openreposhape_package.py: 220 passed in 175.18s. The package suite imports 13 of the launcher's cases and swapsrun_cmd, so it is in the set.tests/test_repo_hygiene.py,tests/test_workflow_concurrency.py,tests/test_release_workflow.py: 138 passed in 14.46s.tests/test_repo_hygiene.py, 297 passed in 188.95s.tests-git-bashruns, with Windows'write_textemulated: a plugin made everyPath.write_textin the test process write CRLF, as it does onwindows-latest. 162 passed in 121.06s (97 + 47 + 18). A probe test shows the plugin takes effect (it passes with the plugin and fails without it).tests.ymlalone and on the four files the CI job names: exit 0.The Windows run
d91b314's own
tests-git-bashrun was green onwindows-latest. It ran the first round's 21 new cases andtests/test_install_sh_filter_repo.pythere, so Git Bash's owncygpathanswered the backslash respelling on a real drive path, and the five existing tests that passstr(...)and now go through it passed. What was checked, and what was not:#!/bin/shruns under Git Bash with no mode bits, andcommand -vfinds it. A script piped in text mode reaches Git Bash's bash as CRLF, and bash drops the CR. A$OPENREPOSHAPE_BIN_DIRset from Python reaches bash unconverted: tests: a tests-git-bash job runs the launcher's and install.sh's suites in Git for Windows' own bash, so README and the handbook say Git Bash runs the one-line install, tested in CI but for a typed yes, which it cannot take there (#585) #596's probe 4 printedD:\a\_temp\…\bin/openRepoShape, which is why the respelling now runs in those tests.as_posix()) through Git Bash's realcygpath, the refusal's values undertmp_pathon the machine report's$PATH, and the new drive-answer case. If one fails there, its log shows the typed value and the directory.Reproductions
One script ran each row against the base's command and the head's, offline, with a
ghstub first on PATH. Row 3's slash lines ran again for this round.git, a logged-ingh,python,git-filter-repo, nopython3python3 MISSING, thenrun: openRepoShape --preflight …; the log holds onlygh auth statuspython3 present, as python, norun:line; the log holds onlygh auth statusOPENREPOSHAPE_REPO=C:/src/openRepoShapehttps://github.com/C:/src/openRepoShape.git; the doctor'sgit clonetooC:/src/openRepoShape, bothOPENREPOSHAPE_REPO=C:\src\openRepoShapehttps://github.com/C:\src\openRepoShape.git, bothC:\src\openRepoShape, bothOPENREPOSHAPE_REPO=acme/openRepoShapehttps://github.com/acme/openRepoShape.gitOPENREPOSHAPE_BIN_DIR='D:\tools\bin', nocygpathinstalled at D:\tools\bin/openRepoShape,export PATH="D:\tools\bin:$PATH"OPENREPOSHAPE_BIN_DIR='D:/tools/bin', nocygpathinstalled at D:/tools/bin/openRepoShape,export PATH="D:/tools/bin:$PATH"(on Linux, a relative directory namedD:in the working directory)'D:/tools/bin', nothing placedcygpath -uanswering<dir>/d/tools/bincygpathis never askedinstalled at <dir>/d/tools/bin/openRepoShape,export PATH="<dir>/d/tools/bin:$PATH"tests-git-bash's pytest lineRows 1-3 need Windows to be real: there,
pythonis a real python.org install, andcygpathand the drive letter are Git Bash's own. On Linux they are fakes. The first Windows run runs rows 1-3's tests under Git Bash: row 3 with Git Bash's owncygpathon a real drive path, rows 1 and 2 with a fakepythonand a fakegit, so a real python.org install and git's own reading ofC:/…stay unchecked.Mutation probes
Each row reverts one hunk of the launcher, or one part of the BIN_DIR hunk, in a throwaway clone, then runs the 27 new cases.
shape_clone_url's drive-letter armmachine_report'spythonarmpython-alonecygpath -ucall (always refuse)|| spelled=""after thecygpathcallcygpath-fails, both slashes:set -eexits withcygpath's status, 1, not the refusal's 2D:/…arm of the drive case (SHOULD-1)D:\…arm of the drive case[A-Za-z]:*(NIT-1)cygpath-answers-a-drive, both slashes (was green)""no-cygpathandcygpath-fails, both slashes--dropped fromcygpath -u -- "$dir"-u -- <value>Not probed by a test: the
tests.ymlline (CI configuration, checked by actionlint and by this PR's own Windows run), the docstring, andinstall_with_machine'sbash_path(identity off Windows; on Windows it is what keeps four machine-report tests from the new refusal).Merge checks (
git merge-tree --write-tree, head e85573a, after a fresh fetch)tests/conftest.pyhas moved since the base a524542 (new helpers for setup-project's tests), and nothing since the last table's 95780dd. The head merged with main (a probe commit, b6cfafe3) passes the suites below.tests-git-bashpytest line, because its base was older than main's #590; its head now holds main d4a7fab, and it does not changetests.yml. It changesAGENTS.md, README,adopt-project.py, the handbook,docs/cli.md, the release notes and four test or render files, none of which this PR touches.tests/conftest.py; #627 changes README and the handbook)scripts/family.py, and so does #641 against main alone: its own conflict, not an overlap with this PR.fix/645-fixture-copy-maintenance-raceaccc7d2,fix/646-add-member-variant-remedy79754b9,fix/647-branch-left-bump-leg-setupbe1bff9 (#657),fix/648-bootstrap-and-gate-order852c5c4Left out, and why
--preflightis where a stub is told apart.install.sh's own lines. It names a Windows-spelled$OPENREPOSHAPE_BIN_DIRas typed in itspreflight:anddone:lines, though the command it installs now prints/d/…. openRepoShape in Git Bash: themachine:report probespython3alone, a drive-letter$OPENREPOSHAPE_REPOor$OPENREPOSHAPE_BIN_DIRis misread,tests-git-bashskips #598's installer cases, and a doctor test's docstring is stale #649 names--install's PATH note, andinstall.shprints no PATH line. Checklist candidate 1.--help. Its$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_BIN_DIRlines stay true, sodocs/cli.mdis not regenerated.D:,D:toolsand UNC values. Checklist candidate 3.Checklist candidates
install.shechoes a Windows-spelled$OPENREPOSHAPE_BIN_DIRas typed. It buildstarget="$bin_dir/openRepoShape"from the raw value, so in Git Bash withD:\tools\binitspreflight:anddone:lines sayD:\tools\bin/openRepoShape, while the command's own lines say/d/tools/bin/openRepoShape. Both spellings work in Git Bash, so nothing breaks. On a machine with nocygpath, the command's--installnow refuses the value, andinstall.shstops with "the openRepoShape's --install refused (exit 2); its own refusal above says why", which is right but could refuse earlier, before the fetch.--installprintsexport PATH="$dir:$PATH"with the directory between double quotes as it is. A directory holding",$, a backquote or\prints a line that does not do what it says, and one holding:cannot be on$PATHat all. A drive letter was the one case a person in Git Bash meets by default; the rest need a deliberately odd directory name.$OPENREPOSHAPE_BIN_DIRset toD:orD:toolsmatches neither respelling arm, so it is kept as typed and printsexport PATH="D:tools:$PATH", as on the base. A UNC\\server\share\…, as$OPENREPOSHAPE_BIN_DIRor$OPENREPOSHAPE_REPO, is never respelled (the PATH note's\\collapses) or becomes a github.com URL. The package'sclone_urlpasses a UNC path through on Windows (os.path.isabs).Closes #649
🤖 Generated with Claude Code
Summary by Sourcery
Improve Git Bash compatibility for Python detection, repository paths, and installation directories while extending CI coverage for installer behavior.
New Features:
python3orpythonin the machine report.Bug Fixes:
Enhancements:
CI:
Tests: