Repository navigation
install.sh: git-filter-repo with the uv already there where there is no pipx, never pip install --user, and two readinesses at the end, adoption: then done: (#595) - #598
Merged
Conversation
…pe.ref` beside the command it places, and the installed command then fetches setup.sh at that ref, hands setup.sh `--shape-ref` for it and checks the doctor's clone out at it, so a command installed from a release stays on that release while `$OPENREPOSHAPE_REF` still overrides it for one run (#564) An installed command read `$OPENREPOSHAPE_REF` at every run and fell back to main, so from v1.0.0 on, with main carrying v1.1.0 work, a command installed from a release drifted off it within hours. The ref is now NAMED by the variable or by the record, and both places that honoured only the exported variable (the doctor's checkout and setup.sh's `--shape-ref`) honour either. The record is a sidecar file, never an edit to the command: the placed command stays byte for byte the file it was installed from, so the installer's own `(unchanged)` verdict and any copy vendored and checked by sha256 still compare bytes and never see the record. An install with no ref records nothing and removes any record already there, which is the command exactly as before. The ref is checked before anything is placed, and the record is checked when it is read, naming the file and the two ways out. `--version` prints the ref in force. docs/cli.md is regenerated for the `--help` paragraph that says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oShape/releases/latest/download/install.sh | bash`, installs the newest release's openRepoShape pinned to that release, installs git-filter-repo with pipx where pipx is and it is not or prints the one command that would, and ends with `openRepoShape --preflight`; README, the handbook and the v1.0.1 release notes lead with it (#564) Brett Heap, 2026-10-14: "we need it to be more simple to install ... now and after, we need a simple install process", and "v1.0.1 soon". install.sh is attached to every release, so `releases/latest/download/` is always the newest. It reads the newest tag from github.com's `releases/latest` redirect with no gh and no login, asking `gh api` only when the redirect names none; takes a named release as its one argument; fetches that release's `openRepoShape`, checks it is the command, and runs its `--install` with `$OPENREPOSHAPE_REF` set to the release, then asks the installed command's `--version` that it stayed there. Every step says what it did in one line. It never runs sudo. Exit 0 installed and ready, 1 installed but the preflight or the pin needs attention, 2 nothing installed; git-filter-repo never moves it. Everything runs from a `main` called on the last line, so a cut-off download runs nothing. README.md: step 3 and the worked example lead with the one line, the gh api and raw-URL lines stay as the by-hand way at main, and Layout gains install.sh; the cap moves 1422 -> 1450, with the reason in test_readme_is_short_enough_to_be_read. docs/handbook.html says the same, names the new README blob and adds the (#564) clause. install.sh joins SHIPPED_BASH and the macOS job's `/bin/bash -n` step, and one test holds the one line byte for byte in every file that carries it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rds a ref only with `OPENREPOSHAPE_RECORD_REF=1`, which install.sh sets, so a script that exports `$OPENREPOSHAPE_REF` around it gets v1.0.0's install; every curl that runs is HTTPS only, redirects included, at TLS 1.2 or newer; a link to the command reads its record; `--help` answers past a broken record; and install.sh says which failure it met (#564) The review on #577 (RV577, FIX-ROUND) and SonarCloud shell:S6506. openRepoShape: - The ref is recorded only when asked, or when it came from the command's own record (a re-install of itself stays pinned). workBenches exports `OPENREPOSHAPE_REF=pinned-by-workBenches-no-fetch` around `--install`; that no longer becomes a record every later run would fail on. Any other install records nothing and removes a record already there. - The raw-URL fallback curl carries `--proto '=https' --proto-redir '=https' --tlsv1.2`, written on the line. - The record is found beside the FILE, symlinks followed with a portable `readlink` loop (no `-f`, at most 40 links). - A broken record is kept as a fault and refused after the arguments are read, so `--help` still answers; `--version` and every run refuse. A record that is a directory is refused in the usual form, by a run and by an install, before anything is placed. install.sh: - Both curls HTTPS only, redirects included, TLS 1.2 or newer. - Sets `OPENREPOSHAPE_RECORD_REF=1` for the install. - A fetch the server refused (curl exit 22) asks whether the release exists; no answer at all points at the network instead. A preflight that exits 1 names what is missing; any other status says it did not run to the end. - With no pipx, `apt-get` machines are told `sudo apt-get install -y pipx && pipx install git-filter-repo==2.47.0` (printed, never run), because Debian 12 and Ubuntu 23.04+ refuse `pip install --user`. - A command that did not record the tag gets its preflight run with the tag named, not main's setup.sh; the shell-profile notes say so. - The last line is `{ main ${1+"$@"}; }`: a download cut inside it runs nothing. install.sh itself never runs sudo; the wording now says the preflight's offers, some of them sudo, each wait for a typed yes. Tests: HTTPS_ONLY in the call asserts and a guard on every curl that runs in both scripts; the opt-in, links, a directory record and `--help` past a bad record; an upper-case and a relative `location`; a non-default OPENREPOSHAPE_BIN_DIR; the apt-get branch; the network failure; the preflight's two failures; three truncated downloads; S9073 composite asserts split. docs/cli.md regenerated for the `--help` change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the fix round on #577: install.sh itself never runs sudo while the preflight it ends with offers what is missing behind a typed yes, the command is pinned to the release tag only when asked, step 3 folds the pin into one paragraph six lines shorter, AGENTS.md says the one-line install is the human's to run, and the notes gain the steps that attach install.sh to the release (#564) - README.md: step 3's paragraph now carries what "pinned" means and the corrected sudo sentence, and leaves the exit statuses to `install.sh --help` and the release notes; the worked example's lead-in is one line. The cap moves to 1444 (from 1450 in d0cdbf4), with the reason updated in test_readme_is_short_enough_to_be_read. - docs/handbook.html: the same paragraph, the same rule in "For AI assistants", the #564 footer clause updated, and the new README blob. - AGENTS.md: "The one-line install (`install.sh`) is theirs to run: relay it, never run it." under "Never answer an install prompt", paid for by tightening the `--allow-upstream-org` paragraph's middle clause; still 490 lines, every rule kept. - docs/release-notes/v1.0.1.md: the corrected sudo sentence, the opt-in (`OPENREPOSHAPE_RECORD_REF=1`) and what a script that exports `$OPENREPOSHAPE_REF` now gets, HTTPS only, Git Bash supported but its tests skip on the Windows job (a known limit, followed up in v1.1.0), and a "Before tagging (remove at tag time)" section: upload install.sh to v1.0.1, check the `releases/latest/download/install.sh` redirect, never attach it to v1.0.0, and publish a later patch on an older line with `--latest=false`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…follow in order (land, prove the squash tree or dispatch the three-OS run on it, tag it annotated, create the release with install.sh attached and --verify-tag, check the asset by redirect and by bytes), Git Bash is said to be meant to work but untested until #585, and only install.sh's own downloads are said to be HTTPS only (#564) The second fix round on #577 (RV577). The old checklist published before attaching install.sh, could tag main through `gh release create` with no tag, uploaded an unchecked working-tree file and never tested the release commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-564-carry-to-main Conflicts, both resolved to main's side, because main retired the mechanism #577's side used (#519, #551): tests/test_repo_hygiene.py: README.md's cap stays README_CAP_BASE plus tests/readme-cap.d/; #577's inline 1422 -> 1444 raise is not taken here and becomes a ledger file of its own in the next commit. docs/handbook.html: the footer keeps main's frozen history and its closing sentence; #577's "added (#564)" clause and whole-README blob are the retired convention, and the sections' markers carry it instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the one-line install in a ledger file of its own, as main raises it, rather than #577's inline 1422 -> 1444 on release/v1.0 (#564) README.md on main is 1427 lines at its cap (1422 + 5, #550); #564's lines take it to 1449. The reason is #577's, carried with its accounting: ten lines for the two install.sh blocks, nine for step 3's paragraph, two for the by-hand ways kept at main, one for the Layout row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S is a typed yes and the one-line install asks nothing, placing the command and git-filter-repo through pipx without sudo, and the eight sections that read README.md § Starting and § Layout record #564's README after a re-read (#564) What changed in README.md since the markers were set is #564 alone: `git diff f6eaab7 HEAD -- README.md` equals `git diff origin/main HEAD -- README.md` (the § Starting markers were set by #579, f6eaab7), and § Layout moved only by the install.sh row since #551 (5e59e3b) set its marker. Re-read against the merged README, each section in full: - masthead: its fastest path is #577's install.sh line with an accurate summary (newest release, pinned, git-filter-repo where pipx can, ends in the preflight). No change. - start: carries #577's install step, the by-hand ways at main and "Each way installs". One statement was made false: "Every action that changes the machine or the account is a typed yes" stood a few lines below install.sh installing git-filter-repo with pipx unasked. It now scopes the rule to what the tool offers and says what the one-line install does, every fact quoted from README step 3. The machine: block paragraph, the preflight step, Run it and What setup.sh actually runs stay true as written. - partway (the WARNING), ci (private legs), legs (the --explain line), doublepin (the hand-edited pin), park (openRepoTools' line, which --install still prints when install.sh calls it): nothing #564 touched. No change. - layout: carries #577's install.sh row, in README's order; "standard library only" stays true (install.sh is bash, git-filter-repo a tool). No change. Markers: § Starting f0693d8a -> cc78d254 in masthead, start, partway, ci, legs, doublepin, park; § Layout c8be0af4 -> 94701efc in layout; both values as the hygiene check printed them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #580 (#546), dd3f23a. One conflict: tests/test_repo_hygiene.py: #564's one-line-install test and #580's DOCTOR_PASSAGES test were added at the same place; both are kept, #564's first. The launcher, its test, README.md, AGENTS.md, docs/handbook.html and docs/cli.md merged cleanly, each holding both sides' hunks unchanged. README.md stays 1449 lines (cap 1449) and AGENTS.md 490 (cap 490). Every handbook marker of § Starting, § Layout and § Keeping equals its segment's fingerprint in the merged README after a re-read, so none moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yped yes, beside step 3's one-line install, which installs git-filter-repo with pipx where it can (#564) "Nothing is installed without that typed `yes`" became "The preflight installs nothing without that typed `yes`", and the rest of the sentence is kept. The longer clause re-wraps the NOTE's last eight lines into eight, so README.md stays at 1449, its ledger cap, and every line stays within 80 columns. `py setup-project.py --preflight` stays on one line, because test_the_windows_commands_name_files_that_exist reads `py <file>` across whitespace. Re-read against the new wording, the seven sections that read § Starting: start's consent rules ("Every action the tool offers ... is a typed yes", "The one-line install asks nothing ...", "no offer at all") and its four "The tool offers the rest" panels already put the typed yes on the preflight's offers; its `machine:` block line is about --install, and partway's "Nothing is created before the typed yes" quotes the WARNING, not the NOTE; masthead, ci, legs, doublepin and park render nothing from the NOTE. No page text changes. The markers move as the hygiene check printed: cc78d254 -> c9aebc08 in masthead, start, partway, ci, legs, doublepin, park. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ore its own two acts, and the preflight it ends with asks before each offer (#564) "The one-line install asks nothing" was not true as written: install.sh ends with `openRepoShape --preflight`, which asks `Type yes to continue:` before each offer, as step 3 above it and README.md:239-241 say. Only install.sh's own two acts, placing the command and installing git-filter-repo with pipx, go unasked. Found in #587's review. Handbook text only, in the same five lines: README.md is unchanged, so no marker moves, and the hygiene check asks for none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ere there is no pipx, a machine with neither is shown pipx from its own package manager or uv, never `pip install --user`, and the run ends `adoption:` then `done:`, which says "ready to scaffold, not yet to adopt" where git-filter-repo is missing and names the next command (#595) On Raven (Arch Linux, uv and no pipx) the v1.0.1 installer printed `python3 -m pip install --user git-filter-repo==2.47.0`, which an externally managed Python refuses (PEP 668), then ended "this machine is ready", and adoption could not run. - Step 3 installs git-filter-repo==2.47.0 with pipx where pipx is there, as before, and otherwise with `uv tool install` where uv is there. Either is one attempt, and installed means `command -v git-filter-repo` finds it afterwards; a directory not on PATH yet is named with the tool's own fix, `pipx ensurepath` or `uv tool update-shell`. - With neither, the printed command is brew's, pipx from apt-get, dnf or pacman, or, with no package manager it knows, `uv tool install` with uv's installation page. It never runs any of them, never installs pipx, uv or a system package, and never runs sudo. - After the preflight, `adoption:` says whether git-filter-repo is there and, where it is not, why and the command to run next. `done:` says "this machine is ready" only when it is, and "ready to scaffold, not yet to adopt" when not. The exit status stays the preflight's and the pin's, because only adopting needs git-filter-repo. tests/test_install_sh_filter_repo.py holds every acceptance case: present on PATH or as `git filter-repo`, pipx only, uv only, both, each installer failing, each installing off PATH, and neither with no package manager, brew, apt-get, dnf or pacman. Each case is held to the installers it runs (and that `sudo`, `python3`, `pip` and the package managers never run), to no `pip install` and no `--user` in the output, and to the two summary lines and exit 0. Two more tests cover a failed preflight with and without git-filter-repo, and a command that does not stay on the release. tests/test_install_sh.py changes only its three hunks that held the old pip and "no pipx" text: the no-pipx case's command, the line printed above it, and the docstring that described both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lter-repo with pipx, or uv, and says last whether the machine is ready to adopt or only to scaffold, and the consent rules say it never installs pipx or uv (#595) README.md's step 3 said install.sh installs git-filter-repo "with `pipx` where it can", which stopped being the whole truth once it uses uv where there is no pipx. The paragraph is re-wrapped in its own eight lines, so the README stays at 1449 lines, its cap, with no ledger entry. The handbook says the same in the two places that render that paragraph, the masthead's fastest path and `start`'s step 3, and `start`'s consent rules now say install.sh installs git-filter-repo with pipx or uv where one is there and never installs either. § Starting a project in a new organisation is the one README segment that moved (blob c9aebc08 -> d55fef86), and seven page parts read it. Each was re-read for install.sh, pipx, uv, git-filter-repo and readiness. The masthead and `start` render the changed paragraph and are amended above. `partway`'s one preflight line is the scaffold's preflight before its yes, which this does not change. `ci`, `legs`, `doublepin` and `park` say nothing about install.sh. Those five render none of the changed text, so their markers moved alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe installer now uses an already-available pipx or uv to install pinned git-filter-repo without ever invoking pip --user or privileged installation, reports adoption readiness separately from scaffold preflight readiness, and adds comprehensive behavioral tests plus synchronized documentation. Sequence diagram for install.sh dependency setup and readinesssequenceDiagram
participant User
participant Install as install.sh
participant Tool as pipx_or_uv
participant Preflight as openRepoShape_preflight
participant Path as PATH
User->>Install: Run installer
Install->>Path: Check git-filter-repo
alt git-filter-repo already available
Path-->>Install: Found
else pipx available
Install->>Tool: pipx install git-filter-repo==2.47.0
Tool-->>Install: Success or failure
Install->>Path: command -v git-filter-repo
alt Not on PATH
Install-->>User: Run pipx ensurepath, then open a new shell
end
else uv available and no pipx
Install->>Tool: uv tool install git-filter-repo==2.47.0
Tool-->>Install: Success or failure
Install->>Path: command -v git-filter-repo
alt Not on PATH
Install-->>User: Run uv tool update-shell, then open a new shell
end
else Neither tool available
Install-->>User: Print package-manager or uv tool install command
end
Install->>Preflight: Run --preflight
Preflight-->>Install: Scaffold readiness and exit status
Install-->>User: Print adoption readiness
Install-->>User: Print done readiness
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…uv's command carries no `# comment` an interactive zsh hands to uv and `pipx ensurepath` is never followed by a comma, pacman's is Arch's own `git-filter-repo`, apt-get's and dnf's end in `pipx ensurepath`, and a failed pipx is the one attempt even with uv beside it (#595) The review on #598 (RV598), its four SHOULDs and nits, in this commit: - The `adoption:` line ends "Before adopting a repository, <what to do>", each caller in its own words, so a command is after `run: ` at the end of the line, or between backticks: - a failed install: `run: <the install>`; - off PATH: run `pipx ensurepath` (or `uv tool update-shell`), then open a new shell; - neither tool: `run: <the command>`, or, with no package manager this knows, `install uv (<uv's page>), then run: uv tool install git-filter-repo==2.47.0`. Step 3's own line says the same before the indented command. At 1a44805 the uv command ended ` # once uv is installed: <url>`, which an interactive zsh with default options passes to uv as arguments (uv exits 2), and the off-PATH line put `pipx ensurepath, then open a new shell` in the `run:` slot. - pacman: `sudo pacman -S --needed git-filter-repo`, Arch's own package, on PATH in one step. Arch's `extra` holds git-filter-repo 2.47.0-3 (archlinux.org/packages/search/json, read 2026-10-15). pacman cannot pin it, and the comment says so. - apt-get and dnf: `… && pipx install git-filter-repo==2.47.0 && pipx ensurepath`. pipx 1.17.12's `ensurepath` exits 0 whether the directory was already on PATH, was added, or was added before. tests/test_install_sh_filter_repo.py: - every case with a next step now pastes the command off its `adoption:` line into bash on the run's PATH, every program it names a logging fake, and holds the calls it makes and that it holds no `#`. Fed the two old lines, this check fails on both: the argv of the first, and the `#` of the second, which bash itself would have read as a comment; - a new case, pipx_failing_uv_there, holds "one attempt, with one tool"; - uv_failing has the fake exit 2, so `(exit $status)` is the tool's own; - the file no longer ends in a blank line. tests/test_install_sh.py changes the no-pipx and apt-get lines it expects, and reads the line above them up to "before adopting a repository, ", because the uv case says "install uv (…), then run:" there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gain, as before #595, on its own line (#595) The review on #598 asked for "itself" back in README.md and the handbook where it fits without a new line. In the handbook's step 3 it fits on the line it was taken from, at 100 columns beside the paragraph's 101. In README.md it does not: wrapped at the paragraph's 80 columns, the eight lines become nine, and README.md is at its 1449-line cap, so README.md keeps "`install.sh` never runs `sudo`". No README.md segment changes, so no marker moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #597 (654c476, CI on Linux only), with #565 and #555 before it. Three conflicts: .github/workflows/tests.yml: main's file whole. #587's `/bin/bash -n install.sh` and its comment lived in the macOS job #597 removed; no bash 3.2 step is added anywhere, as #597 decided, and this pull request no longer touches tests.yml. README.md: the macOS sentence keeps #587's `install.sh` in the list and takes #597's wording: "`setup.sh`, `install.sh` and the `openRepoShape` command are written to use no syntax newer than that, though CI runs on Linux only and no run of it checks them against bash 3.2", with "all three entry points" after it. Four lines, as both sides had: README.md stays 1449 lines, its cap with tests/readme-cap.d/564.txt (+22). docs/handbook.html: the macOS panel's sentence says what the merged README says. The seven markers that read README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park) move to a0e0f23c306cebdf627d90225e33c277f05de64b, as the hygiene check printed; the macOS sentence is the only part of that section the merge changed, and no other part of the seven speaks of CI's runners or bash 3.2. The footer is the same on both sides. test_install_sh.py's pin check still holds: the Linux job installs git-filter-repo==2.47.0, install.sh's pin. The tests' dated history that names the macOS job stays, as #597 left it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main bcfa364: #588 (709eec1, POSIX-quoted paste lines and README's Windows sentence) and #569 (bcfa364, setup-project.py's paste lines). One conflict: docs/handbook.html: the seven markers that read README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park) conflicted, this branch's a0e0f23c against #588's 95f06438. Both moved to the merged section's blob, d77a1e59fe4951aa2a6f96808725d0506d4ce357, as the hygiene check printed. Both sides' prose merged on its own: #588's POSIX-quoting sentence in the Windows panel beside #587's install step, NOTE and macOS sentence. Re-read: nothing in the seven sections is made false by the other side. README.md and docs/cli.md merged cleanly with both sides' hunks, and `render-cli-reference.py --check` passes. README.md is 1449 lines (cap 1449) and AGENTS.md 492 (cap 492, raised by #566 on main). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…book.html's seven § Starting markers move to the merged README's blob (#595) #587 landed on main as 22eacee, and its branch, this PR's base, is gone. This merges its final head first, as the stacked-PR-after-squash procedure says. The one conflict was docs/handbook.html: the seven markers for README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park). Both sides changed that segment: - this branch, step 3's install.sh paragraph; - #587, the macOS bash 3.2 sentence (#597) and the Windows paste-quoting sentence (#588). All seven move to the merged README's blob, d1666fa6. § The three legs changed on #587's side alone, and its markers already carry the merged blob. Both sides' prose merged with no conflict. This branch's step 3 and consent rule are where they were, and #587's two new sentences sit elsewhere in `start`. handbook_marker_problems reports nothing. README.md is 1449 lines, at its cap of 1422 + 5 + 22. AGENTS.md is 492, at its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…files keep this branch's side, proved byte-equal to main's first (#595) main's merge base with this branch is 62ef256, from before install.sh, so four files #587 added or changed conflicted: - README.md, docs/handbook.html (content); - install.sh, tests/test_install_sh.py (add/add). For each, main's blob was proved equal to #587's final head (3d8fa05), merged in the commit before this one. This branch's side is that head plus this PR's own changes, so it is kept. Taking main's side would have dropped the PR. 22eacee's tree is `git merge-tree --write-tree 2b6dbb7 3d8fa05`, and it differs from 3d8fa05's only in adopt-project.py and tests/test_adopt_root_symlinks.py, which a main commit after 2b6dbb7 changed. Both come in here as main's blobs. The result differs from the commit before it in those two files alone, and from main only in this PR's five: README.md, docs/handbook.html, install.sh, tests/test_install_sh.py and tests/test_install_sh_filter_repo.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of this PR's files moved (#595) main moved past 22eacee by 77bb5f0 (bootstrap.py) and 55faad2 (adopt-project.py and shape-doctor.py), neither touching this PR's five files. The result differs from main only in README.md, docs/handbook.html, install.sh, tests/test_install_sh.py and tests/test_install_sh_filter_repo.py. Every file it changes from the commit before is main's blob. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of this PR's files moved (#595) main moved past 55faad2 by fbbbc8d (adopt-project.py, scripts/shape_materialize.py and its test), touching none of this PR's five files. Each of those three comes in as main's blob, and the result differs from main only in this PR's files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
brettheap
marked this pull request as ready for review
October 15, 2026 21:34
|
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 1 day and 1 hour by commenting |
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…ll.sh sentences and #606's three sentences both stay in README and the handbook, so the page's seven § Starting markers read the merged section's hash (#606) Merges main 396c0ff (#570, #598 and #624 since this branch's base, fbbbc8d). Only #598 touches README.md and docs/handbook.html; #570 and #624 change code and tests, taken as main has them. README.md merged without a conflict. #598 rewrote its step-3 paragraph in README § Starting a project in a new organisation, and #606 adds a sentence to the part-way box in the same section, so that section's hash is neither branch's (#606: 46538e8e, #598: d1666fa6) but a third one, 51702a2b. docs/handbook.html conflicted in seven hunks, all of them the one marker line of the seven page parts that read that section (masthead, start, partway, ci, legs, doublepin, park). The text around the markers merged without a conflict, so both sides' sentences are kept: #598's pipx-or-uv and readiness words in the masthead, start and conventions, and #606's partway and adopt mirror sentences. Each of the seven markers now names 51702a2b8c5451e2d027943074a836be0ce009e4, the merged README section's fingerprint, computed with the hygiene test's own readme_segments and readme_fingerprint. The § Adopting marker, ca4b6731, stays: #598 does not touch that section. README is 1456 lines against a cap of 1422 + 5 (550.txt) + 22 (564.txt) + 7 (606.txt) = 1456. #598 added no cap file, so the cap is unchanged by the merge. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…ix/585-git-bash-windows: README's step 3 keeps #598's pipx-or-uv paragraph and this branch's "in Git Bash it can take none" inside its 1449-line cap, the handbook's consent rule keeps both, and the seven markers move to the merged README (#585) Main moved from 77bb5f0 to 396c0ff: 55faad2 (#591), fbbbc8d (#572), 7fdec0a (#624), 3fb21d9 (#598, lane 3's #595) and 396c0ff (#570). Only #598 touches this branch's files: README step 3's install.sh paragraph, the handbook's masthead and `start`, install.sh and tests/test_install_sh.py, and it adds tests/test_install_sh_filter_repo.py. Two files conflicted. README.md, one region: step 3's install.sh paragraph. #598 rewrote it for uv and the closing readinesses; this branch had added "in Git Bash it can take none" after "each behind a typed `yes` at your terminal". Side by side that is nine lines where both have eight, and README is at its cap (1422 + 5 for 550.txt + 22 for 564.txt = 1449). The cap is not raised. Two rewordings fit it, and no fact is dropped: - "`install.sh` is attached to every release and installs the newest, with no `gh` and no login." became "`install.sh`, attached to every release, installs the newest with no `gh` and no login." - #598's "`install.sh` never runs `sudo`;" became "It never runs `sudo` itself;". "It" is install.sh, the subject of the sentence before it, and "itself" is #564's own word for the contrast with the preflight. #598's "It installs `git-filter-repo` with `pipx`, or `uv`, where it can, or prints the command, runs `openRepoShape --preflight`, and says last if the machine is ready to adopt or only to scaffold" is word for word, and so is this branch's "; in Git Bash it can take none". The eight lines rewrap at 80 characters at most, as 78 README lines already are. The Git Bash clause is still true after #598. Under `curl ... | bash`, install.sh's `[ -t 0 ] || exec </dev/null` (install.sh:342, which #598 does not touch) leaves the preflight no terminal on stdin. Under Git Bash setup-project.py runs on native Windows Python, where `open_terminal()` returns None (`os.name == "nt"`), so `can_ask()` is False, and `offer()` takes no `yes`. #598 changes what install.sh does before and after the preflight, not how the preflight reads an answer. docs/handbook.html, eight regions: - The consent rules' install.sh item keeps #598's text, "installs `git-filter-repo` with `pipx` or `uv` where one is there, and itself never runs `sudo` or installs either of them", and then this branch's clause, word for word: "; in Git Bash it can take no `yes`, so it installs nothing." - The seven markers for README.md section "Starting a project in a new organisation" (this branch's 278a1947 against #598's d1666fa6) each record badbb6b273215042ba0e8ae167fd760dc25430ee, the merged section's hash from tests/test_repo_hygiene.py's own readme_segments and readme_fingerprint. All 23 markers match the merged README. The masthead merged as text: this branch's "macOS, Linux, WSL2, Git Bash" heading and PowerShell line, beside #598's "where `pipx` or `uv` can install it ... and whether the machine is ready to adopt or only to scaffold". The `start` step-3 paragraph is #598's; it is `data-platform="linux mac wsl"`, as before. tests/test_install_sh.py merged as text. #598's hunks (`no_pipx` and `no_pipx_but_apt`'s printed commands, the docstring, and the line before the printed command, which now starts "there is neither pipx nor uv") sit beside this branch's `bash_path` in `present_on_path` and in the preflight line of the same test. tests/test_install_sh_filter_repo.py comes in unchanged. It builds on `Net`, whose `fake` this branch writes as LF bytes, and it is not added to the tests-git-bash job. Against main this merge differs only in this branch's six files: .github/workflows/tests.yml, README.md, docs/handbook.html, tests/conftest.py, tests/test_install_sh.py and tests/test_openreposhape_command.py (436+, 147-). `git merge-tree --write-tree HEAD origin/main` gives this commit's own tree. tests/test_repo_hygiene.py, tests/test_readme_sections.py and tests/test_readme_windows_quoting_sentence.py: 144 passed on the merged tree. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 15, 2026
…es 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) **Fix round 7: main after #598 (3fb21d9) (head 52524fe).** Lane 3's #598 (#595) landed on main as 3fb21d9. It rewrote README step 3's `install.sh` paragraph and the handbook's masthead and `start` for `uv`, and for the two readinesses install.sh now ends with. It also changed `install.sh` and `tests/test_install_sh.py`, and added `tests/test_install_sh_filter_repo.py`. One merge commit, **52524fe3**, merges main 396c0ff: 55faad2 (#591), fbbbc8d (#572), 7fdec0a (#624), 3fb21d9 (#598) and 396c0ff (#570). No other change. - **Conflicts.** README.md had 1 region (step 3's `install.sh` paragraph). docs/handbook.html had 8: the consent rules' `install.sh` item and the seven § Starting markers. `tests/test_install_sh.py` merged as text, and so did the handbook's masthead and `start`. - **The head holds all of main.** `git merge-tree --write-tree 52524fe origin/main` gives 52524fe's own tree, 6d8ffea. `git diff --stat origin/main 52524fe` names only this PR's six files (436+, 147-). **The README and handbook resolution with #598 (52524fe).** - **README, before.** This PR (4c3b72a) had added "; in Git Bash it can take none" after step 3's "each behind a typed `yes` at your terminal". #598 rewrote the same paragraph: "It installs `git-filter-repo` with `pipx`, or `uv`, where it can, or prints the command, runs `openRepoShape --preflight`, and says last if the machine is ready to adopt or only to scaffold. `install.sh` never runs `sudo`; the preflight offers what is missing, each behind a typed `yes` at your terminal." - **Fitting the cap.** Keeping both made nine lines where #598 has eight, and README was at its cap. The cap was not raised. Two rewordings fit it, and no fact is dropped: - "`install.sh` is attached to every release and installs the newest, with no `gh` and no login." became "`install.sh`, attached to every release, installs the newest with no `gh` and no login." - #598's "`install.sh` never runs `sudo`;" became "It never runs `sudo` itself;". "It" is install.sh, the subject of the sentence before it, and "itself" is #564's own word for the contrast with the preflight. - **README, after** (8 lines, at most 80 characters, as 78 README lines on main already are): ``` `install.sh`, attached to every release, installs the newest with no `gh` and no login. Its command is PINNED to the release tag, recorded in `openRepoShape.ref` beside it: `setup.sh` and the standard are fetched at that tag, not at main. It installs `git-filter-repo` with `pipx`, or `uv`, where it can, or prints the command, runs `openRepoShape --preflight`, and says last if the machine is ready to adopt or only to scaffold. It never runs `sudo` itself; the preflight offers what is missing, each behind a typed `yes` at your terminal; in Git Bash it can take none. `| bash -s -- v1.0.1` names a release; the same line again upgrades. ``` #598's `pipx`/`uv` sentence and this PR's clause are word for word. README is **1449** lines, its cap (1422 + 5 for `550.txt` + 22 for `564.txt`). - **The Git Bash clause is still true after #598.** Under `curl … | bash`, `[ -t 0 ] || exec </dev/null` (install.sh:342; #598 moved it down from :277 and left its text alone) leaves the preflight no terminal on stdin. In Git Bash, setup-project.py runs on native Windows Python, where `open_terminal()` returns None (`os.name == "nt"`, setup-project.py:1372). So `can_ask()` is False, and `offer()` takes no `yes`. #598 changes what install.sh does before and after the preflight, not how the preflight reads an answer. - **Handbook, consent rules.** The `install.sh` item keeps #598's text, "installs `git-filter-repo` with `pipx` or `uv` where one is there, and itself never runs `sudo` or installs either of them", and then this PR's clause, word for word: "; in Git Bash it can take no `yes`, so it installs nothing." - **Handbook, masthead and `start`.** They merged as text: this PR's "macOS, Linux, WSL2, Git Bash" heading and PowerShell line, beside #598's "where `pipx` or `uv` can install it … and whether the machine is ready to adopt or only to scaffold". The `start` step-3 paragraph is #598's. It is `data-platform="linux mac wsl"`, as before, so it carries no Git Bash clause. - **The seven § "Starting a project in a new organisation" markers.** This PR's 278a1947 conflicted with #598's d1666fa6. All seven now record `badbb6b273215042ba0e8ae167fd760dc25430ee`, computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`. That driver reproduces both parents' hashes (main d1666fa6, 4c3b72a 278a1947). All 23 markers match the merged README. - **`tests/test_install_sh.py`.** #598's hunks are `no_pipx` and `no_pipx_but_apt`'s printed commands, the test's docstring, and the line before the printed command, which now starts "there is neither pipx nor uv". They sit beside this PR's `bash_path` in `present_on_path` and in the same test's preflight line. Both sides' tests keep their meaning. **Fix round 6: retarget to main after #587 (22eacee) (head 4c3b72a).** #587 squash-landed on main as 22eacee at 2026-10-15T20:27:52Z, and GitHub retargeted this PR to `main`. 22eacee's tree, 831e71d, is `git merge-tree --write-tree 2b6dbb7 3d8fa05`: main holds exactly #587's final head 3d8fa05. So this round adds two merge commits and no other change: - **3080ac0d** merges #587's final head, 3d8fa05. It conflicted only in README.md and docs/handbook.html, with lane 4's #588 (709eec1), which 3d8fa05 had merged from main. The resolution keeps both sides' text (below). - **4c3b72ae** merges main, 77bb5f0: 22eacee, plus 2b6dbb7 (#584), which the squash carried but 3d8fa05 lacked, and #615's 77bb5f0, which landed after it. Four files conflicted, because the squash is not an ancestor of 3d8fa05 (merge base 62ef256). For each of them, main's copy is the same blob as 3d8fa05's, and 77bb5f0 does not touch it. Each therefore takes this branch's copy, which is 3d8fa05's copy plus this PR's change: | File | `3d8fa05e:` = `origin/main:` (77bb5f0) = `22eaceed:` | |---|---| | README.md | d164131 | | docs/handbook.html | 59ec34d | | tests/test_install_sh.py (add/add) | 34c7de5 | | tests/test_openreposhape_command.py | 5e49547 | No file was merged by hand. `git merge-tree --write-tree 4c3b72a origin/main` gives 4c3b72a's own tree, 97843ec, so the head holds all of main. `git diff --stat origin/main 4c3b72a` names only this PR's six files: `.github/workflows/tests.yml`, README.md, docs/handbook.html, `tests/conftest.py` and the two suites (430+, 141-). **The README and handbook resolution with #588 (3080ac0).** #588 ends README's "Windows, natively" and the handbook's Windows panel with one sentence: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash. Only `shape-doctor.py`'s lines, `setup-project.py`'s family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `git rm -r --cached` lines are quoted for PowerShell, on Windows." That sentence now follows this PR's Git Bash sentences, word for word. `tests/test_readme_windows_quoting_sentence.py` holds the handbook to README's words, and it passes. - **README**, before (3d8fa05): "That two-liner IS the Windows way in; the `openRepoShape` command in step 3 below is a macOS, Linux and WSL2 convenience with no Windows twin, because on Windows there is nothing to install. Printed commands are quoted …" - **README**, after: "That two-liner IS the Windows way in from PowerShell, which has no twin of step 3's `openRepoShape` command. Git Bash runs that command and step 3's one line, tested in CI but for a typed `yes`, which its preflight cannot take there. Printed commands are quoted …" - **Fitting the cap.** The two texts side by side came to seven lines where #588 had six, and README was at its cap. So "no twin of the `openRepoShape` command in step 3 below" became "no twin of step 3's `openRepoShape` command". Step 3 is "Install the `openRepoShape` command", so the meaning is the same. The six lines rewrap, and README stays at **1449** lines, its cap (1422 + 5 for `550.txt` + 22 for `564.txt`). No raise. - **#588's other README hunk**, the tighter `python setup-project.py` and "Two commands rather than one pipe" paragraphs, merged as text. - **Handbook Windows panel.** It keeps this PR's "There is no `openRepoShape` command to install in PowerShell … so none of its offers can be accepted there." and then #588's sentence, unchanged. - **The seven § "Starting a project in a new organisation" markers** (this PR's 34723d9a against #588's d77a1e59) now record `278a19479524fe8aae292daaca84492902b140ed`, the merged section's hash. It was computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`, and all 23 handbook markers match the merged README. - **Consistency.** Every Windows, Git Bash, PowerShell and WSL2 sentence was read again in the merged text. #588's sentence is true in Git Bash too: the named tools run under native Windows Python there, where `os.name` is `nt`, so their lines are quoted for PowerShell, "on Windows", as it says. See checklist candidate 7. **Fix round 5 (head fe58d66).** One comments-only commit to `tests.yml`, for the re-review's N1 and N3. The header says the FULL suite runs on Linux only, so read alone it stays true beside the exception. The job's comment gives the measured "about a minute and a half (86 s on the first run, 50 s of it pytest)" where it said about three minutes. **Fix round 4 (head 5f001de).** Brett Heap merged #597 ("ci: run the test suite on Linux only") at 2026-10-15T16:54:43Z, which removed the Windows job these suites ran in. His word at 2026-10-15T18:20:35Z: "Option 1: keep a small Git Bash job for #596". That round added two commits: - **f6552862** merged lane 3's `l3-564-carry-to-main` b23b245 (#587's branch, this PR's base until fix round 6), which held main 654c476 and so #597. `tests.yml` became that branch's, byte for byte: the guard step and the macOS comment edit went with the two jobs #597 removed. The handbook keeps #597's macOS panel beside this PR's Git Bash text, and its seven markers were moved. - **5f001de1** adds the `tests-git-bash` job, and fixes the macOS sentence that #597 made say "CI runs on Linux only". v1.0.1's one-line install (#564) is meant to work in Git Bash on Windows, but nothing ran it there: `tests/test_openreposhape_command.py` and `tests/test_install_sh.py` wore `WINDOWS_SKIP`. They now run in the `tests-git-bash` job, under Git Bash. **Which bash.** `BASH` in `tests/conftest.py` is Git for Windows' `usr\bin\bash.exe`, found from `git --exec-path`, never from PATH. Probe 1 on `windows-latest` showed why: - A bare `bash` through `subprocess` is System32's WSL launcher. It prints "Windows Subsystem for Linux has no installed distributions." in UTF-16. CreateProcess searches System32 before PATH. - `shutil.which("bash")` finds `Git\bin\bash.EXE`. That launcher puts Git's `mingw64\bin` and `usr\bin` first on PATH, so Git's real `curl` came before the suite's fake one. The old install.sh suite, run that way, reached github.com and printed the real newest release: `release: v1.0.1, the newest release of opensoft/openRepoShape (https://github.com/opensoft/openRepoShape/releases/latest)`. A `git_bash_session` fixture puts Git's `mingw64\bin` (or `clangarm64\bin` on ARM64), `usr\bin` and `MSYSTEM` where Git Bash puts them, behind any fake a test puts first. `BASH_SKIP` replaces `WINDOWS_SKIP` in these two files only. **What the tests now do on Windows.** - **Paths.** Every path handed to bash, or expected in what it prints, is spelled the way Git Bash spells it (`/d/a/...`). The conversion uses Git's own `cygpath` (`bash_path`, `native_path`). Git Bash converts `HOME`, `TMPDIR` and `PATH` itself. - **Line endings.** Every script a test writes for bash is written as LF bytes. The fake's redirect headers and its `gh` tag are written as bytes: Python on Windows prints `\r\n`, and `$(...)` keeps the `\r`. - **The executable bit.** Git Bash's drives are mounted `noacl`, so `chmod 755` changes nothing. A file that starts with `#!` runs anyway. `--install`'s output is checked by mode 755 on POSIX, and by bash's own `test -x` on Windows. - **A test's own PATH.** On Windows it holds `#!/bin/sh` wrappers. A symlink to a Git Bash program cannot load its DLL: with symlinks, `mktemp` failed and install.sh refused `could not make a temporary directory`. - **An argument holding a newline** reaches Git Bash through the environment. On the Windows command line it was split in two: `v1\nforged` arrived as "two versions given". In Git Bash, bash hands bash the argument whole, so the test now does the same. - **`ln -s`.** A new test makes a link with Git Bash's own `ln -s`, after dropping any `$MSYS` the caller set. By default that is a copy, which has no `openRepoShape.ref` beside it and so follows main. With `MSYS=winsymlinks:nativestrict` it is a real link, which reads the record. - **Two anchors (fix round 2).** - The fetched-setup.sh test now also asserts that the parent of the stub's directory exists. Its "the directory is gone" line therefore cannot pass on a path Python cannot open. Both asserts read one `ran_dir = native_path(ran_in)` (fix round 3), so the existence check cannot be reverted to `Path(ran_in)` alone. - The `gh` shim test already could not pass with the shim unreachable: it asserts that the shim's own log holds exactly `auth status`, and only the shim writes that log. Probe 4 reverted its `os.pathsep` and it still passed: the shim stayed reachable either way, most likely through MSYS's conversion of PATH, which the log does not show. **The workflow (fix round 4).** - **The header** keeps #597's text and adds one sentence: "One exception, on Brett Heap's word at 2026-10-15T18:20:35Z, "Option 1: keep a small Git Bash job for #596": the `tests-git-bash` job below runs the launcher's and the installer's suites, and nothing else, on Windows under Git Bash." - **`tests-git-bash`**: `runs-on: windows-latest`, `timeout-minutes: 15`, and no `if:`, so it runs on every trigger the workflow has: a push to main, a pull request, a manual run. Its steps: 1. checkout and setup-python, at the ubuntu job's two commits, with `fetch-depth: 0`; 2. `python -m pip install --disable-pip-version-check pytest==8.3.4`; 3. the git identity, appended to `GITHUB_ENV` in PowerShell, as the old Windows job did; 4. "Git Bash is the bash the launcher and installer suites run": fails the job when conftest finds no Git Bash, and prints the bash the suites will run; 5. `python -m pytest -q -rA tests/test_openreposhape_command.py tests/test_install_sh.py --basetemp=${{ runner.temp }}\t`. - **Its env** is the old Windows job's: `PYTHONUTF8=1`, `core.autocrlf=false` and `core.longpaths=true` through `GIT_CONFIG_COUNT`. - **Its comment** says why it exists (#585, the one-line install on Windows), why it runs two suites only (#597), why its bash is Git's `usr\bin\bash.exe` (pointing to conftest), and why it runs on a push to main. **The docs.** - **README § "Windows, natively" and the handbook's Windows panel** say: PowerShell has no twin of the command; Git Bash runs it and the one-line install, tested in CI but for a typed `yes`, which the preflight the one-line install ends with cannot take there. #588's sentence about POSIX-quoted printed commands follows (fix round 6). - Why: install.sh:342 (:277 before #598) runs `exec </dev/null` under `curl … | bash`, and setup-project.py's `open_terminal()` has no Windows path. - Nothing installs without a typed `yes`, so consent holds. - Fix round 4: this sentence was left with nothing behind it once #597 removed the Windows job. `tests-git-bash` makes it true again, so its text is unchanged. - **README's macOS sentence and the handbook's macOS panel (fix round 4).** #597 made them say "though CI runs on Linux only and no run of it checks them against bash 3.2". With this job that is false. They now say "though no CI run, on Linux or in Git Bash on Windows, checks them against bash 3.2". README rewraps inside the same four lines. - **Step 3's sentence about the preflight's offers** (README) and **the consent rules** (handbook) add that in Git Bash it can take no `yes`. Since fix round 7, both clauses follow #598's `pipx`-or-`uv` text, and README's paragraph is reworded to fit (above). - The handbook's own step-3 install paragraph is `data-platform="linux mac wsl"`, so it is already scoped away from Windows, and is left. - **The WSL2 sentence** says WSL2 "runs every bash entry point in Linux itself", instead of being "still how you run" them. - **The handbook's masthead** lists Git Bash beside macOS, Linux and WSL2. - **README keeps its line count**: 1449, the cap (1422 + 5 for `550.txt` + 22 for `564.txt`). - **The seven handbook markers** for README.md § "Starting a project in a new organisation" record its new hash, `badbb6b273215042ba0e8ae167fd760dc25430ee` (fix round 7, after #598). Fix round 4's merge set them to `a88ac2d0…`, 5f001de moved them to `34723d9a…` for the macOS sentence, and fix round 6 to `278a1947…` after #588. Every hash was computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`. - **A separate small job, on Brett Heap's word.** At 2026-10-15T18:20:35Z he chose "Option 1: keep a small Git Bash job for #596". The option read: "A small Windows job for just these two suites. The probe runs took 2-3 minutes end to end, so it finishes well inside the 34-36 minute Linux job and adds no wall-clock time. The README claim stays true. It is an exception to #597's decision, so it needs your word." So the job runs these two suites and nothing else. It is not a matrix entry on the ubuntu job, whose actionlint download is a linux-amd64 binary. - **Every trigger, a push to main included.** A squash lands a tree no pull-request run tested (#122), and these two suites are the only run of the one-line install on Windows. At about a minute and a half beside the ubuntu job (86 s on its first run), it adds no wall-clock time. - **What the job keeps from the old Windows job, and why.** - `PYTHONUTF8=1`: the suites decode bash's output with `text=True`, which uses the locale's code page (cp1252) outside UTF-8 mode, and the launcher's `--help` prints em dashes (openRepoShape:108, :119-120). - `core.autocrlf=false` and `core.longpaths=true`: every Windows run of these suites had both, #585's four probes included, and no run without them exists. - `fetch-depth: 0`: `test_a_ref_the_doctor_would_check_out_is_validated` sets `OPENREPOSHAPE_REPO` to this checkout, so the launcher clones it, and every Windows run of it cloned a full one. - `--basetemp=${{ runner.temp }}\t`: the old job's short temp root, for MAX_PATH. - **What it leaves out.** - **git-filter-repo.** These suites never reach a real one. The install suite's PATH holds only its own fakes, `git-filter-repo` and `pipx` among them. The launcher's machine report gets a fake `git-filter-repo` on the PATH `machine_path` builds. On Linux, with every PATH directory that held a `git-filter-repo` removed (`filter-repo=none` in the run log), the two suites gave **113 passed**. `test_it_installs_the_git_filter_repo_ci_runs` still finds the ubuntu job's pin. - **actionlint.** The ubuntu job lints this file. - **`usr\bin\bash.exe`, not `bin\bash.exe`, and not PATH.** The probe-1 lines quoted above. - **`git_bash_session` is needed, though the runner's own PATH hides it.** - The runner's PATH already holds Git's `bin`, `usr\bin` and `mingw64\bin`, with `bin` before System32 (probe-1 PATH dump). So reverting the fixture changes nothing there (probe 2: 113 passed). - A stock Git for Windows install puts only `Git\cmd` on PATH. Probe 3 ran the two files on exactly that PATH, where `which mktemp` is `None` and `which bash` is `C:\Windows\system32\bash.EXE`: - with the fixture: **113 passed**; - with it reverted: **59 failed, 15 passed, 39 errors**, for example `bash: line 199: mktemp: command not found` and `this machine has no mktemp, which the install needs`. - **`bash -s` runs still take their script through a text-mode pipe.** That pipe hands bash CRLF on Windows, but Git Bash's bash drops a carriage return before a newline: a CRLF script piped to it printed `crlf-ok` and exited 0 (probe 1). Sending bytes instead changed no result (mutation `text-stdin`, 113 passed), so that change was dropped. - **Python can make symlinks on the runner** (`os.symlink: ok`), so the three existing symlink tests run there unchanged. - **The macOS sentence names Git Bash without a line.** "No CI run, on Linux or in Git Bash on Windows, checks them against bash 3.2" is true with this job, and fits README's four lines. A clause naming the two suites would have needed a fifth line and a cap raise. - **Not claimed: that setup's own `yes` is asked in Git Bash.** Typed at a Git Bash prompt, `openRepoShape <Project>` reads its `yes` through native Windows Python. Whether that Python sees a console depends on Git Bash's pseudo-console support, and nobody has confirmed it on a Windows machine. - **Linux, on the head 52524fe** (fix round 7, `dirty=0`): - the two suites: **113 passed** in 41.23s; - `tests/test_install_sh_filter_repo.py` (#598's, unchanged): **18 passed** in 7.32s; - `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py`, `tests/test_setup_by_hand_commands.py` and `tests/test_readme_windows_quoting_sentence.py`: **171 passed** in 25.43s. - `git grep -l 'install.sh' tests/` finds `conftest.py`, `readme-cap.d/564.txt`, `test_install_sh.py`, `test_install_sh_filter_repo.py` and `test_repo_hygiene.py`. Every test file among them was in these runs. No skips. - The merge, before its commit: `test_repo_hygiene.py`, `test_readme_sections.py` and `test_readme_windows_quoting_sentence.py`, 144 passed. - **actionlint 1.7.12 on 52524fe**, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. `tests.yml` is the same blob as at 4c3b72a (5fc46de); #598 does not touch it. - **Linux, on the head 4c3b72a** (fix round 6, `dirty=0`): - the two suites: **113 passed** in 28.57s; - `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py`, `tests/test_setup_by_hand_commands.py` and `tests/test_readme_windows_quoting_sentence.py`: **171 passed** in 17.09s. - Those cover every `tests/test_install_sh*.py` (there is one) and every test file `git grep -l 'install.sh' tests/` finds (`test_install_sh.py`, `test_repo_hygiene.py`; the other two hits are `conftest.py` and `readme-cap.d/564.txt`). - 3080ac0's merge, before its commit: `test_repo_hygiene.py`, `test_readme_sections.py` and `test_readme_windows_quoting_sentence.py`, 144 passed. - **actionlint 1.7.12 on 4c3b72a**, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. The workflow keeps main's #593 `concurrency` block byte for byte (this PR's two `tests.yml` hunks are the header and the new job), the header's "The FULL suite runs on LINUX ONLY" with the exception sentence, and `tests-git-bash`. - **Linux, on the head 5f001de** (`dirty=0`): the two suites, `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py` and `tests/test_setup_by_hand_commands.py`: **281 passed** in 61.24s. - **Linux, fix round 4, before its commit:** - the two suites: 113 passed; - the two suites with no `git-filter-repo` on PATH: **113 passed**; - the four docs and hygiene files: 168 passed; - the merge alone, before its commit: `test_repo_hygiene.py` and `test_readme_sections.py`, 141 passed. - **actionlint 1.7.12**, fetched and checksum-checked as the ubuntu job does, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. - **The YAML parse.** Triggers: `push` to `main`, `pull_request`, `workflow_dispatch`. Jobs: `tests` (ubuntu-latest) and `tests-git-bash` (windows-latest, `timeout-minutes: 15`). Neither has an `if:`, so both run on every trigger. - **Linux, earlier rounds:** on 01e8c2c, the same five files without `test_setup_by_hand_commands.py`, 276 passed. Lane 5's re-review got the same 276 there. On 649b089, with `tests/test_setup_by_hand_commands.py`, `tests/test_agents_md_codes.py` and `tests/test_gh_guard.py` as well, 334 passed. - **Windows, `windows-latest`, through probe branches that ran only these suites:** - Probe 1 (run 38752845751): 109 passed, 2 failed, 0 skipped. The two failures were the newline arguments, fixed since. - Probe 2 (run 38754067543, tree ebba347): **113 passed, 0 skipped**, in 68.83s. - Probe 3 (run 38757083883, on 649b089): **113 passed** in 50.97s. On a stock Git for Windows PATH (only `Git\cmd`): 113 passed. - Probe 4 (`l5-585-probe-4`, on 01e8c2c): **113 passed** in 77.92s (run 38769166462). - **The same two files as they were at 5191f0e, with only `WINDOWS_SKIP` lifted** (probe 1): 88 failed, 23 passed. Two causes: the WSL launcher, and the real network through `Git\bin\bash.EXE`. - **Windows on 5f001de, the PR's own run of `tests-git-bash`:** **113 passed** in 50.28s, with the whole job taking 1 min 26 s (18:37:00Z to 18:38:26Z, check run 116601758252). - The guard step printed `C:\Program Files\Git\usr\bin\bash.exe`. - Its steps and env are probe 4's, less the git-filter-repo install, and with `-q -rA` and a 15-minute job timeout. Probe 2 ran these on **ebba347f**, the tree before the squash, which still had a `run_piped` helper; the `text-stdin` row is that helper. The squashed head 649b089 was then re-measured whole by probe 3 (113 passed), and fix round 2's head 01e8c2c by probe 4. Each row reverts one Windows hunk and runs the two files again. | Hunk reverted | Result | |---|---| | `BASH` is the bare word `bash` | 108 failed | | paths keep Windows' spelling | 25 failed | | scripts written with `write_text` (CRLF) | 1 failed: `test_a_release_whose_command_records_no_ref_exits_one`, whose launcher's first line became `#!/usr/bin/env bash\r` and was refused as not the command | | the fake prints its headers and tag in text mode | 22 failed | | mode 755 checked on Windows too | 2 failed | | symlinks on a test's own PATH | 28 failed | | newline arguments on the command line | 2 failed | | no `git_bash_session` | 113 passed on the runner's PATH; on a stock Git for Windows PATH (probe 3) 59 failed and 39 errors, against 113 passed with it | | `bash -s` script sent as bytes (`run_piped`) | 113 passed either way; this hunk was dropped | Fix round 2's anchors (probe 4, on 01e8c2c): | Run | Result | |---|---| | the fetched-setup.sh test, `native_path(ran_in)` reverted to `Path(ran_in)` in both asserts | exit 1: 1 failed. The new parent-directory anchor catches it. Since fix round 3 both asserts read one `ran_dir`, so this is the only way to revert it. | | the `gh` shim test, its PATH joined with `:` instead of `os.pathsep` | exit 0: 1 passed. The shim stays reachable, so the hunk is not load-bearing on this runner. The log shows only that outcome; MSYS's conversion of PATH is the likely reason. The test is still not vacuous: it asserts that the shim's own log holds exactly `auth status`, and only the shim writes that log, so an unreachable shim would fail it. | | both `ln -s` cases with `MSYS=winsymlinks:nativestrict` exported, head | exit 0: 2 passed | | the same, with the `MSYS` drop reverted | exit 1: 1 failed (`ln-default` made a link), 1 passed | #590 adds four cases to these two suites, written while they skipped on Windows: - `test_a_command_that_is_a_symlink_is_refused_before_any_request[live|dangling]`; - `test_install_refuses_a_target_that_is_a_symlink[live|dangling]`. Each compares a path bash printed with `str(path)`. Each also compares `os.readlink`'s answer with the target, and on Windows that answer typically starts `\\?\` (Python's `os.readlink` documentation, since 3.8; the probe's failures stop at the first assert, so no log shows it). Linux passes on the merged tree (117 passed), but under Git Bash those four cases fail, and `tests-git-bash` runs them. **Whichever of #596 and #590 lands second applies these hunks** to the merged tree. Probe 4 (run 38769166462) applied exactly these. ```diff --- a/tests/test_install_sh.py +++ b/tests/test_install_sh.py @@ -668,12 +668,14 @@ def test_a_command_that_is_a_symlink_is_refused_before_any_request( result = net.run() assert result.returncode == 2, result.stdout + result.stderr said = " ".join(result.stderr.split()) - assert f"{link} is a symbolic link, so nothing was installed" in said, said + assert f"{bash_path(link)} is a symbolic link, so nothing was installed" \ + in said, said assert "pipx upgrade openreposhape" in said assert "pipx uninstall openreposhape" in said assert net.calls() == [], net.calls() assert link.is_symlink() - assert link.readlink() == packaged + # Windows reads a link back with `\\?\` ahead of its target. + assert str(link.readlink()).removeprefix("\\\\?\\") == str(packaged) if not dangling: assert packaged.read_bytes() == original assert sorted(path.name for path in net.bin.iterdir()) == \ --- a/tests/test_openreposhape_command.py +++ b/tests/test_openreposhape_command.py @@ -407,17 +407,18 @@ def test_install_refuses_a_target_that_is_a_symlink(tmp_path, dangling): link = bin_dir / "openRepoShape" link.symlink_to(packaged) result = run_cmd("--install", home=tmp_path, - env={"OPENREPOSHAPE_BIN_DIR": str(bin_dir), + env={"OPENREPOSHAPE_BIN_DIR": bash_path(bin_dir), "OPENREPOSHAPE_REF": "v9.9.9", "OPENREPOSHAPE_RECORD_REF": "1"}) assert result.returncode == 2, result.stdout + result.stderr said = " ".join(result.stderr.split()) - assert f"{link} is a symbolic link, so --install will not copy this " \ - "command through it" in said, said + assert f"{bash_path(link)} is a symbolic link, so --install will not " \ + "copy this command through it" in said, said assert "pipx upgrade openreposhape" in said assert "pipx uninstall openreposhape" in said assert link.is_symlink() - assert os.readlink(link) == str(packaged) + # Windows reads a link back with `\\?\` ahead of its target. + assert os.readlink(link).removeprefix("\\\\?\\") == str(packaged) if not dangling: assert packaged.read_bytes() == original assert not packaged.with_name("openRepoShape.ref").exists() ``` Both files already import `bash_path` once #596 is in. On Linux, the merge of 649b089 with d090f0c, with these hunks: **117 passed**. Fix round 7, on the merge of 52524fe with #590's head 1c8a708, with fix round 6's resolved `tests.yml` (both inputs unchanged since; the result is blob 266c62c): the hunks apply (`git apply --check` exit 0). The two suites (117 by collection), `tests/test_install_sh_filter_repo.py` (18), `tests/test_openreposhape_package.py` and `tests/test_repo_hygiene.py` give **270 passed** in 48.69s. Fix round 6: on the merge of 4c3b72a with #590's current head 1c8a708, with `tests.yml` resolved as below, the hunks apply (`git apply --check` exit 0) and the two suites give **117 passed**; with `tests/test_openreposhape_package.py` and `tests/test_repo_hygiene.py`, 252 passed. Probe 4 runs the merge of 01e8c2c with `l3-563-pypi-package` on Windows: the four cases without the hunks, then both suites with them. In run 38769166462, on Windows: - **Without the hunks**: exit 1, `4 failed, 113 deselected`. These are exactly the four cases. The launcher pair failed on `REFUSED: D:\a\_temp\…\bin/openRepoShape is a symbol…`, which confirms rv585's inferred launcher failures; the install pair failed on `REFUSED: /d/a/_temp/…/.local/bin/openRepoShape …`. - **With the hunks**: they apply (exit 0), and both suites give **117 passed** in 75.13s. **`tests.yml` conflicts with #590 (fix round 6; unchanged in fix round 7, where #590's head is still 1c8a708 and this PR's `tests.yml` is the same blob).** #590 now targets `main` too: at fix round 6 its merge ref's first parent was main 77bb5f0. Its head 1c8a708 has merged #587's final head and main, so it holds #597, and it merges with main cleanly. Both PRs add to the same two places in `tests.yml`, so against 4c3b72a there are three regions: - **The header.** Keep this PR's "The FULL suite runs on LINUX ONLY" and its exception sentence, then #590's four lines about the `package` job. - **The end of the file, two regions.** git splits the two new jobs where they share the checkout and setup-python lines. Keep both jobs whole: `tests-git-bash`, then #590's `package`. - **Checked.** I built that file: this PR's `tests.yml` with #590's header paragraph and its `package` job added. Against #590's file it differs only in this PR's two hunks, and against this PR's file only in #590's two. `yaml.safe_load` gives the jobs `['tests', 'tests-git-bash', 'package']`, and actionlint 1.7.12 exits 0 on it. #590's `package` comment now says "the other jobs" and "on Linux like the rest of the suite since #597", which stay true beside `tests-git-bash`. Fix round 4's advice to drop `tests-windows` and `tests-macos` and to reword "three jobs" no longer applies: #590 has done both. The open pull requests are the 17 with a `refs/pull/<n>/merge` ref in `git ls-remote origin 'refs/pull/*'`: 583, 590, 594, 596, 599, 614, 617-623 and 625-628. Each head was merged with 52524fe and, separately, with main 396c0ff. The ones not in the table merge cleanly with this head and touch none of its six files: #583, #614, #617, #618, #619, #621, #622, #625, #626 and #628. | Against | Result | |---|---| | origin/main 396c0ff (#591, #572, #624, #598 3fb21d9, #570) | **clean**. The merge gives this head's own tree, 6d8ffea: the head holds all of main. | | #590 1c8a708 (`l3-563-pypi-package`) | **conflicts** in `.github/workflows/tests.yml`, 3 regions: the header and the end of the jobs. #590 against main alone is clean, so this is the two PRs' own overlap. It also touches `tests/test_install_sh.py` and `tests/test_openreposhape_command.py`, which merge as text. See the #590 section above for the resolution, the four test cases and fix round 7's 270 passed. | | #599 b0182e2 (lane 3, stacked on #590's old head d090f0c) | **conflicts** in `.github/workflows/tests.yml`, 2 regions. #599 also conflicts with main alone, in six files (`tests.yml`, README.md, the handbook, `install.sh`, `openRepoShape`, `tests/test_install_sh.py`). It is #597's conflict, through #599's stale base, not this job's. | | #627 fc0656b (`fix/606-readme-followups`, #606; new since fix round 6) | **conflicts** in docs/handbook.html only: 7 regions, each one § Starting marker line (this PR's badbb6b2 against #627's 51702a2b). Its README hunks (the second-run refusal in § Starting, and `CONTRACTS_DIR` and the `single-repository.yaml` question in § Adopting) merge as text with this PR's. #627 against main alone is clean. **Whoever lands second** sets the seven markers to the merged README's hash. On this head plus fc0656b that is `fe0d932b031e4a08fc885f93d6cd978df0d5a25c`. README is then 1456 lines: 1449 plus #627's own `readme-cap.d/606.txt` (+7). | | #594 be808a7, #623 1e30fa7 | **clean**. Both touch `tests/conftest.py`, which merges as text. | | #620 4c049a8 | **conflicts** in `adopt-project.py`, and so does #620 against main alone: its own conflict with main, not an overlap with this PR. | #598 landed as 3fb21d9 and is in the main row; fix round 7 is that merge. Every lane-5 PR and every other PR that has landed is in the main row. - **`tests/test_setup_sh.py`, and `test_repo_hygiene.py`'s `bash -n` test, still skip on Windows,** and since #597 nothing runs them on Windows at all. #585 names the launcher's and the installer's suites, and Brett Heap's word keeps a job for those two only. The same `BASH` would serve both. - **Any typed `yes`.** CI has no terminal. - **`tests/test_install_sh_filter_repo.py` in `tests-git-bash` (fix round 7).** #598's file is not added to the job; that is the coordinator's call. See checklist candidate 8. - **`docs/release-notes/v1.0.0.md` and `docs/release-notes/v1.0.1.md`** are records of those releases, and #597 left them unedited too. v1.0.1's notes say #585 runs the suites under Git Bash; that stays true. The v1.1.0 text is below. - **A required check.** This PR does not make `tests-git-bash` a required status check. That is a ruleset setting, and Brett Heap's call. No v1.1.0 notes file exists on main yet, so this PR writes none. Proposed text, a section of its own: > **Git Bash on Windows (#585).** Git Bash, the bash that Git for Windows installs, runs the one-line install and the `openRepoShape` command. CI's `tests-git-bash` job runs their suites under Git for Windows' own bash, with the PATH Git Bash makes, and fails if it cannot find that bash. That job is an exception to #597's Linux-only CI, made on Brett Heap's word. In PowerShell the way in is unchanged: `py setup-project.py`. Two things differ from macOS and Linux. `ln -s` in Git Bash makes a COPY unless `MSYS=winsymlinks:nativestrict` is set; a copy has no `openRepoShape.ref` beside it, so it follows main, and `openRepoShape --version` says so. Link it with that setting (Windows lets a non-administrator make a symlink only in Developer Mode), or put `~/.local/bin` on `PATH` instead. And `chmod` changes nothing on Git Bash's drives; a file that starts with `#!` runs as a command anyway. Not tested there: `setup.sh` run on its own (its suite still skips on Windows), and any typed `yes`, for which CI has no terminal. In Git Bash the preflight that the one-line install ends with cannot take a `yes` at all, so it installs nothing for you. 1. **The machine report's `python3` line on Windows.** `--install`'s `machine:` block probes only `python3`. Where Python came from python.org, Git Bash has `python` and no `python3`, or only the Microsoft Store stub. The block then says `python3 MISSING`, or "present" for a stub, while `--preflight` passes with `python`. 2. **A drive-letter `$OPENREPOSHAPE_REPO`.** `shape_clone_url` treats `C:/src/openRepoShape` as an owner/name and builds `https://github.com/C:/src/openRepoShape.git` from it. Git Bash and git itself both accept the drive-letter spelling. 3. **A Windows-spelled `$OPENREPOSHAPE_BIN_DIR`.** `--install` then prints `export PATH="D:\...:$PATH"`, which bash cannot use: the drive's colon splits it. 4. **A stale docstring.** `test_a_ref_the_doctor_would_check_out_is_validated` says the refusal arrives before any clone. `shape_checkout` clones first, then checks the ref. 5. **On Windows, the preflight a piped install ends with cannot take a typed `yes`.** - Under `curl … | bash`, install.sh:342 (:277 before #598) hands its children /dev/null for stdin. - setup-project.py's `open_terminal()` returns None when `os.name == "nt"`, so it has no Windows path to the person's terminal. Opening `CONIN$`, where POSIX opens `/dev/tty`, might be the analogue. - Found by rv585; it is the root of this PR's README wording. 6. **The handbook's step 3 has no Git Bash path.** Its install block and paragraph are `data-platform="linux mac wsl"` (docs/handbook.html:775-784 on 01e8c2c), so a reader on the Windows tab never sees the one-line install that the Windows panel says Git Bash runs. Adding it to the Windows tab is a docs decision. 7. **In Git Bash, the host-quoted lines are still quoted for PowerShell.** `openRepoShape --doctor`, a family `setup-project.py` run and `family.py`, started from Git Bash, run under native Windows Python, where `os.name` is `nt`. So `quote_arg`, `shell_quoted`, `shell_line` and `shell_word` (for example setup-project.py:2331, `if os.name == "nt" and not posix`) quote for PowerShell, even though the person is at a bash prompt. #588's sentence stays true ("on Windows"). But someone who follows "Git Bash runs that command" and pastes such a line back into Git Bash gets the failure that `tests/test_readme_windows_quoting_sentence.py`'s docstring names: `git -C C:\work\Orbit` reads as `C:workOrbit`. Choosing `sh`'s dialect under Git Bash (for example on `MSYSTEM`) would be a #532 decision. 8. **#598's installer cases do not run under Git Bash.** `tests-git-bash` runs `tests/test_install_sh.py`, and since fix round 7 that suite reaches #598's code in two ways: every run now prints `adoption:` and `done:`, and `no_pipx` expects uv's printed command. But the uv install, the readinesses' wording and the pasted next steps are tested in `tests/test_install_sh_filter_repo.py` only, on Linux. README says Git Bash runs step 3's one line, "tested in CI". - **What the file uses.** It inherits `test_install_sh.pytestmark` (`BASH_SKIP` and `git_bash_session`), and drives every run through `Net`: `fake` (LF bytes through `write_script`), `run` (`BASH`, `bash_path(INSTALLER)`), `serve`, `installed` and `calls`. Its own `uv` fake writes LF bytes, like `Net.fake`, and names the interpreter as `shlex.quote(sys.executable)`, as `Net.fake` does in the job today. No assert compares a path bash printed with a Python path; each checks a line's start or end. - **Under Git Bash it would likely pass.** Its programs see `PATH=net.path` alone, so the runner's own pipx or uv cannot leak in. The launcher's `--install` probes `python3` with `command -v` and never runs it, so the logging `python3`/`python` fakes stay silent. Its `paste()` sends a script through a text-mode pipe, as `Net.run(piped=True)` already does in the job. - **Not run on Windows.** On Linux: 18 passed in 7.32s. Adding it to the job is one more path on the job's pytest line. 9. **README still names `pip install git-filter-repo` for adopting.** README:768 (§ Adopting) says "`git filter-repo` is a **hard dependency** — `pip install git-filter-repo`, or the same name from apt or brew." #595 found that `pip install` is refused on an externally managed Python (Debian 12, Ubuntu 23.04, Arch; PEP 668), and #598's install.sh no longer prints it. Neither #598 nor this PR touches that line. Review: rv585 found 0 blockers, 2 should-fix and 8 nits on 649b089 (CHANGES NEEDED), fixed or answered in 01e8c2c; the re-review rv585b found 5 nits on 01e8c2c (APPROVE WITH NITS), fixed or answered in 3095a9c; rv585c found 4 nits on 5f001de (APPROVE WITH NITS), fixed or answered in fe58d66. CI (Brett Heap's ruling 27, Linux-only since #597): H0 is the head 52524fe, a hand-resolved merge of main 396c0ff, green on tests (Linux), tests-git-bash and SonarCloud; main since then (#625 8f453d0, #583 de428f5) touched none of this PR's files, and the local gate passed on the predicted squash over de428f5: the launcher's, the installer's and install.sh's filter-repo suites, 131 passed; and the hygiene, README-section, CLI-reference, setup-by-hand and Windows-quoting sets, 171 passed. The tests-git-bash job is on Brett Heap's word of 2026-10-15T18:20:35Z, "Option 1: keep a small Git Bash job for #596". Landed on Brett Heap's word of 2026-10-15T22:24:36Z, "Merge #596 when its turn comes". Closes #585 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9 of 10 tasks
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
Takes #570, #598, #624 and #625 (main since fbbbc8d, this PR's base) so the branch is tested against the main it will land on. The merge is clean: the branch changes adopt-project.py in the follow-up lines and the contracts rule and main in the pin, `--org` and readiness code, with no hunk in common (#600). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
…ests, 57 of 82 milestone issues closed), with #583, #596, #614, #619, #594, #617 and #618 in their areas, #596's Git Bash facts for its placeholders, release.yml's step 1 whole, and every pull request cited beside its issue (RV630 SHOULD 3, 5, 6, nits 7, 8) - SHOULD 5: step 1 matches release.yml's header: "always on a `release/v<X.Y>` branch, which no push run tests", "A `release/v<X.Y>` branch serves while it is still at that commit", and that a tag's dispatch running at the tag's commit is GitHub's documented behaviour, not yet seen here. - SHOULD 6: #598 beside #595, #592 beside #593, #460 on #557's line, and #589's line says "where that clone is still on the machine and is no partial clone" again. The counts and the open-issue list are re-derived from `git log --first-parent bfed143..origin/main` (49) and the milestone (25 open, 57 closed: #342, #392, #393, #421, #423, #426, #432, #433, #434, #443, #444, #454, #455 and #629 joined it). New lines under adopt-project.py for #583 (#520, #420), #619 (#530), #618 (#418), #614 (#527), #594 (#526) and #617 (#525); #596 (#585) under the one-line install, CI, native Windows, the docs and the known limits, where the two placeholders for it were. - SHOULD 3: the known limit about "the installed doctor's temporary clone" is gone; the README, the handbook and AGENTS.md no longer say it. - SHOULD 1: the filter-repo line says install.sh's order, apt and dnf included. - nit 7: the release process's preconditions are said as the process this release follows; the workflow checks the environments, and nothing checks the rehearsal. - nit 8: the known limits are wrapped. - "the first on native Windows, where the one line cannot run" says PowerShell cannot run it, since Git Bash does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 16, 2026
brettheap
added a commit
that referenced
this pull request
Oct 16, 2026
…-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) #596's Git Bash follow-ups, the five lines of #649. Two commits on main a524542: **d91b3144**, the fix, and **e85573ac**, fix round 1 from rv649's review. Together they change `openRepoShape`, `.github/workflows/tests.yml` and `tests/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 and `docs/cli.md` are unchanged: every sentence about these blocks stays true (below). ### 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. - **SHOULD-1, the `D:/…` arm had no test.** Both BIN_DIR tests now run over `D:\tools\bin` and `D:/tools/bin`, and `fake_cygpath` takes the typed value. On Windows they use `str(...)` and `.as_posix()` of a path under `tmp_path`. With the arm dropped the suite stayed green (91 passed); now 5 cases fail. - **NIT-1, the refusal's drive-answer arm had no test.** A third refusal case: a `cygpath` that answers `D:/tools/bin` with exit 0. With that arm dropped the suite stayed green; now 2 cases fail. - **NIT-2, a `python` that is Python 2, or older than 3.9, reads as present.** Named as a cost in decision 1 and in the comment above `machine_report`. No code change: `--preflight` still refuses those machines. - **NIT-3, the refusal's reason was false in Git Bash with `cygpath` off `PATH`.** Git Bash does read `D:\…` as a drive. The comment in `install_commands` and decision 3 now give the true reason. - **NIT-4, the job's opening comment named two files.** It names the three. - **NIT-5, on Windows the refusal test's value was a real `D:` drive path.** It is now a path under `tmp_path`, and the assert names that value, so a refusal that regressed would place the command where the test looks. - **NIT-6, four claims.** Fixed in this body: "rows 1-3 are checked through the tests" (now says what runs), the clone URL for each slash (`C:\src\…` gave `https://github.com/C:\src\openRepoShape.git`), the Store stub clause (dropped, see decision 1) and the report's diffstat totals (the exact `--numstat` counts 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. - **Composition and merge table.** Refreshed against origin/main 879d804 (below). #630 no longer conflicts. - **Checklist candidate 3**, from rv649, is added below. ### The fix **1. The `machine:` row for Python** (`machine_report`). - Before: the block asked `command -v python3` and nothing else. Where Python came from python.org, Git Bash has `python` and no `python3`, so the row said `python3 MISSING` and printed the line pointing at `--preflight`, which then passed with `python`. - Now: it asks for `python3`, then `python`, in `find_python`'s order. When only `python` answers, the row says `python3 present, as python`, and nothing points at `--preflight` for it. With neither, it says `MISSING`, as before. With `python3`, it says `present`, byte for byte as before. **2. A drive-letter `$OPENREPOSHAPE_REPO`** (`shape_clone_url`). - Before: `C:/src/openRepoShape` and `C:\src\openRepoShape` were read as an owner/name, so setup.sh's clone and `--doctor`'s were handed `https://github.com/C:/src/openRepoShape.git` for the first and `https://github.com/C:\src\openRepoShape.git` for the second. - Now: a letter, a colon and then `/` or `\` is a path, and goes through as it is, as `/…` and `.…` do. No owner/name holds a colon. The package's own `clone_url` (`src/openreposhape/standard.py`, `DRIVE_RE`) already read a drive letter this way. **3. A Windows-spelled `$OPENREPOSHAPE_BIN_DIR`** (`install_commands`). - Before: `--install` named the directory as typed. With `D:\tools\bin` it printed `installed at D:\tools\bin/openRepoShape` and `export PATH="D:\tools\bin:$PATH"`, which the drive's colon splits in two. It also never found the directory on a `$PATH` that holds `/d/tools/bin`, so it printed that line every time. - Now: a drive-letter value, in either slash, is spelled again with `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`. - Where no `cygpath` answers (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-bash` runs `tests/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_validated` said the refusal "has to arrive before any clone does". The command clones first, then checks the ref, and refuses before `git checkout` is handed it. The docstring now says that. The order is unchanged. - **Line 1: `command -v` for both names, and no version check.** - The block promises to execute nothing but `gh auth status`, and `test_the_machine_report_runs_no_installer` holds it to that: every fake on its PATH logs a run. Running `find_python`'s check would break that promise. - What answers is therefore never asked its version, and that has a cost. The Microsoft Store's `python3` stub reads as present, as macOS's Command Line Tools stubs for `python3` and `git` already do (setup.sh's comment at :41-47). That is not new: the base asked `command -v python3` too, and the Store installs its `python.exe` and `python3.exe` aliases together, so `python3` answers first. - What is new is a `python` that is Python 2, or older than 3.9, where there is no `python3`. On the base such a machine said `python3 MISSING` and printed the `--preflight` pointer. Now it says `present, as python` and prints no pointer. It is rare (CentOS 7, macOS 10.14 or older, Python 2.7 from python.org), and `--preflight` still refuses it with "no Python 3.9 or newer on this machine (tried python3, python)". The comment above `machine_report` says so. - The row keeps its name `python3`, and the state carries the name that answered, in the form `gh`'s row already uses (`present, logged in`). So README's sentence ("`git`, `gh` and whether it is logged in, `python3`, and `git-filter-repo` … each `present` or `MISSING`") and the handbook's stay true, and README is not edited. Labelling the row `python` would have made both false in Git Bash. - **Line 3: respell, and refuse only where respelling is not possible.** Respelling keeps a Git Bash user's value working: `cp`, `mkdir` and the record took the Windows spelling already, and only the printed lines were wrong. Where no `cygpath` answers, nothing in the command can spell the value as bash's `$PATH` needs it: in Git Bash with `cygpath` off `PATH` the drive is real but its colon still splits `$PATH`, and on Linux `D:\tools\bin` is a relative directory with a colon in its name. No `$PATH` line could work there, so it refuses. A `cygpath` that answers a drive spelling again is refused alike. - **The tests' `install_with_machine` now names its directory with `bash_path`.** Its `$PATH` holds only the programs `--install` runs, and no `cygpath`. On Windows the old `str(bin_dir)` is a drive path, which would now be refused. Off Windows, `bash_path` changes nothing. Other tests that pass `str(...)` keep the full PATH, so on Windows they now go through Git Bash's own `cygpath`. - **`run_cmd` takes a `cwd`.** On the base, and under a mutation, the new BIN_DIR tests' `D:\tools\bin` is a relative directory. With `cwd=tmp_path` it lands in the test's own directory, never in the checkout. - **Both BIN_DIR tests run over both slashes** (`BIN_DIR_SPELLINGS`, `typed_bin_dir`). Off Windows the typed value is the fixed `D:\tools\bin` or `D:/tools/bin`. On Windows it is a path under `tmp_path`, so neither test can place a command on the machine's own `D:` drive. New, in `tests/test_openreposhape_command.py`, 27 cases: - `test_the_machine_report_finds_a_python_by_either_name`: `python` alone, 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 its `as_posix()`) and Git Bash's own `cygpath`; elsewhere a fake `cygpath` answers as that one would. - `test_a_windows_spelled_bin_dir_is_refused_where_no_cygpath_spells_it`: no `cygpath`, a `cygpath` that fails, a `cygpath` that answers `D:/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:\…` and `d:\…`, each through setup.sh (a setup.sh that prints `$OPENREPOSHAPE_REPO`) and through `--doctor` (a `git` that 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 swaps `run_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. - The head merged with main 879d804 (a probe commit, b6cfafe3): the launcher's, the two installer files, the package suite and `tests/test_repo_hygiene.py`, **297 passed** in 188.95s. - **The three files `tests-git-bash` runs, with Windows' `write_text` emulated**: a plugin made every `Path.write_text` in the test process write CRLF, as it does on `windows-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). - **actionlint 1.7.12**, with shellcheck on PATH, on `tests.yml` alone and on the four files the CI job names: exit 0. d91b314's own `tests-git-bash` run was green on `windows-latest`. It ran the first round's 21 new cases and `tests/test_install_sh_filter_repo.py` there, so Git Bash's own `cygpath` answered the backslash respelling on a real drive path, and the five existing tests that pass `str(...)` and now go through it passed. What was checked, and what was not: - **Checked, from #596's Windows runs (113 passed on 5f001de, and its probes):** a fake that Python writes with `#!/bin/sh` runs under Git Bash with no mode bits, and `command -v` finds it. A script piped in text mode reaches Git Bash's bash as CRLF, and bash drops the CR. A `$OPENREPOSHAPE_BIN_DIR` set from Python reaches bash unconverted: #596's probe 4 printed `D:\a\_temp\…\bin/openRepoShape`, which is why the respelling now runs in those tests. - **Checked on Linux:** LF bytes (the CRLF emulation above). No assert in the test file compares a path bash printed with a Python path. - **Not checked, until e85573a's own run:** on Windows, the slash spelling (`as_posix()`) through Git Bash's real `cygpath`, the refusal's values under `tmp_path` on the machine report's `$PATH`, and the new drive-answer case. If one fails there, its log shows the typed value and the directory. One script ran each row against the base's command and the head's, offline, with a `gh` stub first on PATH. Row 3's slash lines ran again for this round. | Row | Base a524542 | Head e85573a | |---|---|---| | 1. PATH with `git`, a logged-in `gh`, `python`, `git-filter-repo`, no `python3` | `python3 MISSING`, then `run: openRepoShape --preflight …`; the log holds only `gh auth status` | `python3 present, as python`, no `run:` line; the log holds only `gh auth status` | | 2. `OPENREPOSHAPE_REPO=C:/src/openRepoShape` | setup.sh clones `https://github.com/C:/src/openRepoShape.git`; the doctor's `git clone` too | `C:/src/openRepoShape`, both | | 2. `OPENREPOSHAPE_REPO=C:\src\openRepoShape` | `https://github.com/C:\src\openRepoShape.git`, both | `C:\src\openRepoShape`, both | | 2. `OPENREPOSHAPE_REPO=acme/openRepoShape` | `https://github.com/acme/openRepoShape.git` | the same | | 3. `OPENREPOSHAPE_BIN_DIR='D:\tools\bin'`, no `cygpath` | exit 0, `installed at D:\tools\bin/openRepoShape`, `export PATH="D:\tools\bin:$PATH"` | exit 2, the refusal above, nothing placed | | 3. `OPENREPOSHAPE_BIN_DIR='D:/tools/bin'`, no `cygpath` | exit 0, `installed at D:/tools/bin/openRepoShape`, `export PATH="D:/tools/bin:$PATH"` (on Linux, a relative directory named `D:` in the working directory) | exit 2, the same refusal with `'D:/tools/bin'`, nothing placed | | 3. the same, a fake `cygpath -u` answering `<dir>/d/tools/bin` | the same as with none: `cygpath` is never asked | exit 0, `installed at <dir>/d/tools/bin/openRepoShape`, `export PATH="<dir>/d/tools/bin:$PATH"` | | 4. `tests-git-bash`'s pytest line | two files | three files | | 5. the docstring | "the refusal has to arrive before any clone does" | "clones this checkout first, then checks the ref" | Rows 1-3 need Windows to be real: there, `python` is a real python.org install, and `cygpath` and 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 own `cygpath` on a real drive path, rows 1 and 2 with a fake `python` and a fake `git`, so a real python.org install and git's own reading of `C:/…` stay unchecked. 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. | Hunk reverted | Result | Failing cases | |---|---|---| | none (the head) | 27 passed | — | | `shape_clone_url`'s drive-letter arm | 6 failed | the six drive-letter cases, setup.sh and doctor | | `machine_report`'s `python` arm | 1 failed | `python-alone` | | the BIN_DIR block, whole | 10 failed | the four respelling cases and the six refusal cases | | the `cygpath -u` call (always refuse) | 4 failed | the four respelling cases | | the refusal (keep the value as typed) | 6 failed | the six refusal cases | | `\|\| spelled=""` after the `cygpath` call | 2 failed | `cygpath-fails`, both slashes: `set -e` exits with `cygpath`'s status, 1, not the refusal's 2 | | **the `D:/…` arm of the drive case (SHOULD-1)** | 5 failed | the two slash respelling cases and the three slash refusal cases (was green) | | the `D:\…` arm of the drive case | 5 failed | the same five, backslash | | **the refusal's drive-answer arm, `[A-Za-z]:*` (NIT-1)** | 2 failed | `cygpath-answers-a-drive`, both slashes (was green) | | the refusal's empty-answer arm, `""` | 4 failed | `no-cygpath` and `cygpath-fails`, both slashes | | `--` dropped from `cygpath -u -- "$dir"` | 4 failed | the four respelling cases: the fake answers only `-u -- <value>` | | the respelling after the placement, not before | 4 failed | the four respelling cases | Not probed by a test: the `tests.yml` line (CI configuration, checked by actionlint and by this PR's own Windows run), the docstring, and `install_with_machine`'s `bash_path` (identity off Windows; on Windows it is what keeps four machine-report tests from the new refusal). | Against | Result | |---|---| | origin/main 879d804 (d4a7fab, then 879d804, since the last table's 95780dd) | **clean**. Of this PR's files, the three suites', README and the handbook, only `tests/conftest.py` has 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. | | lane 3's #630 61b3907 | **clean**. It conflicted at ca5c49f, at the `tests-git-bash` pytest line, because its base was older than main's #590; its head now holds main d4a7fab, and it does not change `tests.yml`. It changes `AGENTS.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. | | #620 9f2b67c, #621 10dda17, #622 55666f3, #623 fc08a28, #627 83ca2db, #628 d64252a, #631 21bdac8, #634 b7472df, #636 30a52c0, #637 62263d2, #638 b393e1f, #639 08b6245, #642 1a42df4, #643 1fe3764, #644 733f853 | **clean** (#623 changes `tests/conftest.py`; #627 changes README and the handbook) | | #641 e87fcb0 | conflicts in `scripts/family.py`, and so does #641 against main alone: its own conflict, not an overlap with this PR. | | lane 5's `fix/645-fixture-copy-maintenance-race` accc7d2, `fix/646-add-member-variant-remedy` 79754b9, `fix/647-branch-left-bump-leg-setup` be1bff9 (#657), `fix/648-bootstrap-and-gate-order` 852c5c4 | **clean**, all four | - **The Store stub check.** See the decision on line 1. `--preflight` is where a stub is told apart. - **`install.sh`'s own lines.** It names a Windows-spelled `$OPENREPOSHAPE_BIN_DIR` as typed in its `preflight:` and `done:` lines, though the command it installs now prints `/d/…`. #649 names `--install`'s PATH note, and `install.sh` prints no PATH line. Checklist candidate 1. - **`--help`.** Its `$OPENREPOSHAPE_REPO` and `$OPENREPOSHAPE_BIN_DIR` lines stay true, so `docs/cli.md` is not regenerated. - **`D:`, `D:tools` and UNC values.** Checklist candidate 3. 1. **`install.sh` echoes a Windows-spelled `$OPENREPOSHAPE_BIN_DIR` as typed.** It builds `target="$bin_dir/openRepoShape"` from the raw value, so in Git Bash with `D:\tools\bin` its `preflight:` and `done:` lines say `D:\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 no `cygpath`, the command's `--install` now refuses the value, and `install.sh` stops with "the <version> openRepoShape's --install refused (exit 2); its own refusal above says why", which is right but could refuse earlier, before the fetch. 2. **The PATH note is not quoted for every directory.** `--install` prints `export 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 `$PATH` at all. A drive letter was the one case a person in Git Bash meets by default; the rest need a deliberately odd directory name. 3. **Two kinds of value slip past both new arms** (rv649). `$OPENREPOSHAPE_BIN_DIR` set to `D:` or `D:tools` matches neither respelling arm, so it is kept as typed and prints `export PATH="D:tools:$PATH"`, as on the base. A UNC `\\server\share\…`, as `$OPENREPOSHAPE_BIN_DIR` or `$OPENREPOSHAPE_REPO`, is never respelled (the PATH note's `\\` collapses) or becomes a github.com URL. The package's `clone_url` passes a UNC path through on Windows (`os.path.isabs`). Review: rv649 found 0 blockers, 1 should-fix and 6 nits on d91b314 (CHANGES NEEDED); the fix round e85573a answers all seven, so both mutations that survived now fail a test, and lane 5's coordinator checked it. CI (Brett Heap's ruling 27, with his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 e85573a passed tests (Linux), tests-git-bash (windows-latest) and SonarCloud; the head 022bd50 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 launcher, installer and package set, 220 passed, and the hygiene, workflow and README-section set, 214 passed. Landed on Brett Heap's word of 2026-10-16T13:07:34Z, "merge #656 and #657 when green". Closes #649 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-3 (openRepoShape-3)
Claim: #595 (comment)
Stacked on #587; lands after it. The base is
l3-564-carry-to-main(#587's branch, head25b2aeed), because #587 is what carriesinstall.shinto main. GitHub retargets this PR to main when #587's branch is deleted.What
Four commits, the last two from the review round below. The head is
f03a192f.3ca08528and274dc070,install.shand its tests:Step 3 uses uv where there is no pipx. It installs
git-filter-repo==2.47.0with pipx where pipx is there, as before. Otherwise it usesuv tool install git-filter-repo==2.47.0where uv is there. It makes one attempt with one tool: a pipx that fails is not followed by the uv beside it. "Installed" meanscommand -v git-filter-repofinds it afterwards. If the tool's directory is not on PATH yet, the line names that tool's own fix:pipx ensurepathoruv tool update-shell, then a new shell.With neither tool, it prints one command and never runs it. The line holds the command alone, so it pastes and runs as printed:
brew install git-filter-repowherebrewis there;sudo apt-get install -y pipx && pipx install git-filter-repo==2.47.0 && pipx ensurepath, and the same withdnf;sudo pacman -S --needed git-filter-repo: Arch's own package, version 2.47.0-3 inextraon 2026-10-15 (pacman cannot pin it);install uv (<uv's installation page>), then run: uv tool install git-filter-repo==2.47.0.The
python3 -m pip install --userfallback is gone.The run now ends with two readinesses. After the preflight it prints
adoption:and thendone:.adoption:says whether git-filter-repo is there. Where it is not, it says why and what to do first, with any command afterrun:at the end of the line or between backticks.done:says "this machine is ready" only when git-filter-repo is there. Otherwise it says "this machine is ready to scaffold, not yet to adopt".The exit status does not change. It stays the preflight's and the pin's, because only adopting needs git-filter-repo.
The privilege boundary holds.
install.shstill never runssudo, and it never installs pipx, uv or a system package. The header,--helpand the comments say so.1a448059andf03a192f,README.mdanddocs/handbook.html: step 3 said install.sh installs git-filter-repo "withpipxwhere it can". It now says pipx or uv, and that the run says last whether the machine is ready to adopt or only to scaffold.start(step 3 and the consent rules) say the same.d55fef86. Each of the seven page parts was re-read, and the commit message says which parts changed text and which only moved their marker.What the run ends with, per acceptance case
Every row is a case in
tests/test_install_sh_filter_repo.py, with a preflight that passed and exit status 0. Eachadoption:line is quoted from a run atf03a192f. EveryNOT READYline beginsadoption: NOT READY: adopt-project.py needs git-filter-repo, and …; the table shows the part afterand.adoption:done:saysgit filter-repoanswers (pipx and uv there, neither run)already installed (…)git-filter-repo is installed, which adopt-project.py needs beyond what the preflight checks.this machine is readyinstalling it with <tool>: …/installed with <tool>readypipx could not install it (exit 1) …… pipx could not install it (exit 1). Before adopting a repository, run: pipx install git-filter-repo==2.47.0ready to scaffold, not yet to adoptuv could not install it (exit 2) …… uv could not install it (exit 2). Before adopting a repository, run: uv tool install git-filter-repo==2.47.0ready to scaffold, not yet to adoptinstalled with <tool>, into a directory that is not on PATH yet: …… pipx installed it into a directory that is not on PATH yet. Before adopting a repository, run `pipx ensurepath`, then open a new shell(uv:uv tool update-shell)ready to scaffold, not yet to adoptthere is neither pipx nor uv …; before adopting a repository, run:and the command… it is not installed. Before adopting a repository, run: <that command>ready to scaffold, not yet to adopt…; before adopting a repository, install uv (<page>), then run:and the command… it is not installed. Before adopting a repository, install uv (https://docs.astral.sh/uv/getting-started/installation/), then run: uv tool install git-filter-repo==2.47.0ready to scaffold, not yet to adoptThe other two outcomes keep their own
done:lines and exit 1, with theadoption:line still above them:Where adoption readiness is reported, and why only install.sh changes
install.sh's final summary: new here.setup-project.py --preflight: has no git-filter-repo row, and not by oversight. Every branch has 0 matches forfilter(main, install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587, The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590, Git Bash on Windows: run the launcher and install.sh tests under Git Bash in the Windows job, so the one-line install is tested there (follow-up to #564) #585). The launcher says why atopenRepoShape:306: git-filter-repo is something "onlyadopt-project.pyneeds and--preflighttherefore does not ask about". The preflight is section (1) of the scaffold, so a row there would make an adoption-only dependency a scaffold prerequisite. The issue's acceptance rules that out.shape-doctor.py'smachinerow: already says "git-filter-repois not on PATH, which onlyadopt-project.pyneeds" (shape-doctor.py,check_machine). The row isn/aby status, so it never moves the verdict.tests/test_doctor_machine_row.pyholds it with and without git-filter-repo. Unchanged.openRepoShape --install'smachine:block listsgit-filter-repo present/MISSING, andadopt-project.py executerefuses withFILTER_REPO_HINT. Both are unchanged.Not changed here: possible follow-ups
[ok] this machine is ready.(setup-project.py:1161-1163), and under install.sh that line sits aboveadoption: NOT READY. The preflight could say "ready to scaffold" instead. Changing it touches the scaffold's and the doctor's wording and tests, so it is not done here.adopt-project.py'sFILTER_REPO_HINTleads withpip install git-filter-repo, and so do README § Adopting and the handbook'sadoptsection. That is the same externally-managed trap, met at the moment adoption refuses.machine:block printsrun: openRepoShape --preflight (offers what is missing; …)when only git-filter-repo is MISSING, but the preflight never offers git-filter-repo.Why
Brett Heap's #595, from Raven (Omarchy / Arch Linux, uv, no pipx, no git-filter-repo).
python3 -m pip install --user git-filter-repo==2.47.0, which Arch's externally managed Python refuses.The issue asks for:
pip --user;Tests
tests/test_install_sh_filter_repo.py(new, 18 tests). It reusestests/test_install_sh.py'sNet, pin and preflight call, and inherits itspytestmark, so one skip policy covers both files. Its one fake of its own isuv. Every case also puts logging fakes forsudo,python3,python,pipandpip3on PATH, plus its own package manager.sudo/pip/package managers run;pip installand no--useranywhere in the output;adoption:thendone:;adoption:line and pasted into bash, with every program it names a logging fake: the calls it makes, and no#in it.adoption:still said).tests/test_install_sh.py: only four hunks change, at base lines 466, 479, 490-494 and 513-516. They are the no-pipx and apt-get commands it expects, the docstring, and the line above those commands, now read up to "before adopting a repository, ". An untouched line stands between them and Git Bash on Windows: run the launcher and install.sh tests under Git Bash in the Windows job, so the one-line install is tested there (follow-up to #564) #585's hunks at 436 and 518.ghexiting 97 first on PATH,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR. Full suite and the macOS and Windows jobs not run locally.test_install_sh.py,test_install_sh_filter_repo.py,test_readme_sections.py,test_repo_hygiene.py,test_setup_by_hand_commands.py,test_cli_reference.py,test_agents_md_codes.py,test_update_add_codes_documented.py,test_doctor_machine_row.py,test_openreposhape_command.py: 358 passed under bash 5.2.21.curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587's owninstall.sh, all 18 new tests fail. Against the reviewed head1a448059, 8 fail: every case whose next step changed.shellcheck -s bash install.shis clean.uv tool install git-filter-repo==2.47.0(uv 0.12.18): into a bin directory not on PATH it exits 0 with a warning; run again it exits 0 ("already installed"); an unsatisfiable pin exits 1.pipx ensurepath(pipx 1.17.12) exits 0 whether its directory is already on PATH, newly added, or added before.Path.write_textwrite\r\n.test_install_sh.py's ownNet.fakewrites#!/bin/sh\rlaunchers, so the installer tests fail there: 26 of 45 existing, and all of the new ones. That trap is Git Bash on Windows: run the launcher and install.sh tests under Git Bash in the Windows job, so the one-line install is tested there (follow-up to #564) #585's to fix, and Git Bash on Windows: run the launcher and install.sh tests under Git Bash in the Windows job, so the one-line install is tested there (follow-up to #564) #585 does.3095a9c0), both installer files pass 63/63 plain and 63/63 under emulation.write_textcarried CRs, while the launchers bash executes are written as bytes and carried none.3095a9c0(the review read01e8c2c0) conflicts in more than the seven § Starting markers. README step 3 and the handbook's consent rule conflict in text too, because 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 adds a Git Bash sentence ("in Git Bash it can take none") to each.test_readme_is_short_enough_to_be_read.d090f0c5merges with no conflict, and the two installer files pass 65/65 on the result.Review (RV598) and fix round
RV598 found no MUST. Fixed in
274dc070(install.sh and tests) andf03a192f(handbook):At
1a448059, uv's printed command ended# once uv is installed: <url>. An interactive zsh with default options passes that to uv as arguments, and uv exits 2.The off-PATH
adoption:line putpipx ensurepath, then open a new shellin therun:slot.Now
adoption_not_readytakes the caller's own instruction:run: <the install>for a failed install;run `pipx ensurepath`, then open a new shell(oruv tool update-shell) off PATH;run: <the command>with a known package manager;install uv (<page>), then run: uv tool install git-filter-repo==2.47.0with none.Step 3's own line says the same before the indented command.
The new paste check fails on both old lines, fed to it verbatim: the off-PATH line on its argv, and the uv line on its
#(bash itself reads it as a comment, which is why that assertion exists).pipx_failing_uv_thereholds that a failed pipx is not followed by the uv beside it.git-filter-repopackage, checked to exist inextra(2.47.0-3) via archlinux.org's package search JSON; it is not pinned.&& pipx ensurepath.uv_failing, so(exit $status)is the tool's own.install.shitself never runssudo"), on its own line at 100 columns. It is not back in README: at the paragraph's 80-column wrap it costs a ninth line, and README is at its cap.y.tab.cpredates the patchedparse.y. The new code uses onlylocal,shift 2,"$@" || status=$?,caseand[ ], and"$@"always has arguments where it is used. The macOS job is the check.Merge of #587's squash and main
#587 landed on main as
22eaceed, its branch (this PR's old base) was deleted, and GitHub retargeted this PR to main. The stacking note at the top is history. The head is noweda2654e, three merge commits onf03a192f, pushed as a fast-forward:09edc24merges install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587's final head3d8fa05e.docs/handbook.html. Both sides changed that README segment: this PR's step 3, and install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587's macOS sentence (ci: run the test suite on Linux only #597) and Windows paste-quoting sentence (bump-leg.py and update-shape.py quote the paste lines that named a root or a path bare, the doctor'squote_argquotes a leading=for zsh, and README says the printed commands are for a POSIX shell (#532) #588).d1666fa6. § The three legs changed on install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587's side alone and already carried the merged blob.handbook_marker_problemsreports nothing.a307230merges main22eaceed.62ef256, from beforeinstall.sh, so four files conflicted:README.mdanddocs/handbook.html(content), andinstall.shandtests/test_install_sh.py(add/add).3d8fa05e's. This branch's side, that head plus this PR, was kept; main's side would have dropped the PR.22eaceed's tree equalsmerge-tree 2b6dbb78 3d8fa05e. It differs from3d8fa05eonly inadopt-project.pyandtests/test_adopt_root_symlinks.py, which come in as main's blobs.9ccc29fandeda2654emerge main55faad26andfbbbc8d0. No conflict, and none of this PR's files touched. Every file they change comes in as main's blob.Against main, the PR is the same change as before. The patches to
install.sh, both test files andREADME.mdare identical. Indocs/handbook.htmlonly the seven § Starting marker values differ (d77a1e59→d1666fa6).Caps.
README.mdis 1449 lines, at its cap of 1422 + 5 + 22.AGENTS.mdis 492, at its cap. Lane 5's #596 also edits README step 3's install.sh paragraph and the handbook. Keeping both texts costs a 1450th line, for whichever of the two lands second.Predicted squash.
git merge-tree --write-tree origin/main HEAD(mainfbbbc8d0) is clean and gives046fa02c, HEAD's own tree.Tests at
eda2654e, with a stubghfirst on PATH,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR:test_install_sh.py45,test_install_sh_filter_repo.py18test_openreposhape_command.py66test_repo_hygiene.py65,test_readme_sections.py76test_readme_windows_quoting_sentence.py3,test_setup_by_hand_commands.py5test_cli_reference.py22,test_agents_md_codes.py47,test_update_add_codes_documented.py6test_doctor_machine_row.py8bashresolving to that build. This replaces the review section's "Not run: bash 3.2".Closes #595
🤖 Generated with Claude Code
Summary by Sourcery
Introduce a release-pinned one-line installer that safely installs openRepoShape, reports separate scaffold and adoption readiness, and supports git-filter-repo through existing pipx or uv tooling without privileged fallback installations.
New Features:
Bug Fixes:
Enhancements:
CI:
Deployment:
Documentation:
Tests: