Repository navigation
setup-project.py: a passed-through --dry-run makes none of the preflight's offers and --shape-ref is held to git's rule part by part, so a rehearsal no longer writes git's global config and a.lock/b is refused before the clone (#857) - #873
Conversation
…-project` is and an empty line says no name was typed, so `../x`, `a/b`, `..` and `--help` are refused by name, not by an `--id` hint, a help page or a probe write (#729) The name typed at the `project name` prompt was used as typed. The same value as `--project` or as the positional goes through `checked_value` at parse time and is refused there. Typed, it reached step (3), which builds the clone path from it and asks the disk about that path: `..` leaves `--into`, an absolute name replaces it, and #608's write probe is made in the nearest folder above it. Step (4) did not catch it either: the naming validator reads only the last part of what it is handed (`../x` passes as `x`), has no length bound and reads a leading `-` as its own option, so `--help` printed its help and exited 0. The scaffold's plan then refused `../x` as `scaffold-bad-id: --id '../x'`, an id nobody passed, and `-h` with a usage dump. Now `checked_value("project name", typed)` runs right after the `ask`, the check `take_positional` makes, so the refusal is the tool's own wording with `project name` as its subject, exit 2, before anything reads or writes a path. `a b` and `Bad_Name` still go to step (4): `NAME_RE` is permissive on purpose and the policy rules on those. Nothing in step (3) changes, and the validator's basename rule stays as it is. An empty typed line (and Ctrl-D) now says "no project name was typed; nothing was created.", exit 2, with no usage text. It said "no --project given, and no terminal to ask on." after the full usage text, which is the refusal for no terminal, and `can_ask()` still gives that one. tests/test_setup_project_typed_name.py types each name at a pseudo-terminal (POSIX only, as the other terminal tests are). Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… and check are one helper, and the tests pin where the check sits, so a check made after step (3) has asked the disk fails them (#729) Review round on 9f429f4 (rv729: approve with nits, two SHOULDs). SHOULD-1. SonarCloud reported python:S3776 on resolve_project_and_elector (cognitive complexity 16 of 15). The prompt, the empty-line refusal and the `checked_value("project name", typed)` call move into `ask_for_the_project_name()`, with their comments; the call site is `opts.project = ask_for_the_project_name()`. No behaviour changes. The function is back to 14 by rv729's estimator, and the helper is 1. SHOULD-2. The tests did not pin where the check sits: a check moved to just before `check_names`, or to after `resolve_family`, passed all 16 of them. Now every refused name is typed with a file standing where the folder above `--into` goes, so a check made after step (3) asks the disk would end in "is a file" and not "project name is"; and `[ok] project` and `[ok] legs` must not print. Only the seeded file may be on the disk afterwards, unchanged. Both moved checks now fail 12 tests each. Nits. The empty line and Ctrl-D are also typed through the one-liner's shape (stdin a pipe) and through `setup.sh`, 6 tests in place of 2. `(4) naming policy` is one constant. The module docstring no longer says `--help` "exited 0" of the run: the naming validator exited 0 at step (4), and the run ended at step (5) with exit 2. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t name over 95 characters is refused before step (4) where GitHub will hold the repositories, and the prompt takes the spaces off a typed name, so `../x` is no longer told about a leading `-`, a 100-character name no longer ends at step (5) or at GitHub, and `Atlas ` no longer hides in step (4) (#777) `refuse_value` gave every refusal the one closing sentence, and the sentence explained only a leading `-`: `../x`, `a/b`, ` Atlas`, `Atlas/` and a name with an accented letter were told about a `-` none of them has. It now takes the reason, and `why_refused` names the first check the value failed, in the order empty, leading `-` (which keeps its "git reads its own arguments" sentence), control character, then the pattern, said in words and as the pattern, as `repo_shape.checked_value` prints its own. `checked_ref` says `..` and `.lock` in their own sentences and `checked_passthrough` its control character. `repo_shape.checked_value` is not changed: it is a pinned copy, so a wording change there is a re-pin, and it already prints its pattern. A project name over 95 characters is refused before step (1) for `--project` and the positional, and before step (3) prints for the prompt, unless `--local-remote-dir` is given. GitHub keeps a repository name to at most 100 characters (its documentation: nothing here creates a real repository, so the number is not measured) and `-spec` and `-code` add five. Under `--local-remote-dir` the limit is the disk's and step (5) still asks it as `scaffold-name-too-long`. The check runs at the end of the parse, because `--local-remote-dir` can come after `--project` on the line. The prompt takes the spaces off the ends of what was typed. `Atlas ` passed `NAME_RE` where ` Atlas` was refused, and step (4) judged `Atlas -spec` with no space shown anywhere. A line of spaces is now an empty line. An argv value is deliberate and is held as typed. tests/test_setup_project_name_edges.py is new. #756's file moves its three-spaces case from the refusals to the empty answers, and a comment in tests/test_setup_project_py.py stops saying the two entry points refuse in the same words. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…dential helper for github.com, and one run empties git's own config, so a GitHub run no longer waits on CI at a `gh auth setup-git` offer nothing answers (#777) On CI (run 39027235147, git 2.55.0) five of this file's tests were killed after 60 seconds: the four over-the-bound names typed at the prompt (96, 100, 101, 255) and the 95-character name typed in a GitHub run. Each screen ended in step (1): [??] git has no credential helper for github.com, so the first push over HTTPS will fail. Fix it once: gh auth setup-git Run `gh auth setup-git` now? Type yes to continue: A GitHub run (no `--local-remote-dir`) makes that offer in its preflight when git has no credential helper for github.com, before step (3) asks for the project. The tests type only at the project prompt, so nothing answered the offer, and the run waited. The bound played no part: the same names in local runs passed, and the 95-character GitHub run hung too. On the writer's machine /etc/gitconfig and ~/.gitconfig both name a helper, so the offer never came there. CI's runner names none. With git's system file skipped and its global file empty, the same five hang on that machine with CI's screen. The tool is right: the offer is `_check_credential_helper`'s documented warning and offer. The tests depended on the machine's git config. - `gh_that_is_signed_in` also gives git a credential helper for github.com, in git's command scope (`GIT_CONFIG_COUNT`, appended to what the environment holds): `!<the stub> auth git-credential`, the line `gh auth setup-git` writes, pointed at the stub. The preflight finds a helper on every machine. No run here pushes, so git never asks it. - A new test, `test_a_github_run_here_is_not_held_by_a_git_with_no_helper` (95 and 96 characters), runs with git's system and global files empty, as CI's runner has them. A run that waits at the offer now fails on every machine, not on CI alone. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the name and ref sentences say ASCII, so a typed tab, form feed or no-break space is refused as `--project` refuses it, and `Caf\xe9` is no longer described as spellable (#777) Lane 2's review of #811 (rv811, at 854f6ed), SHOULDs 2 and 3 and NITs 4 and 5. Its MUST 1 is b5def3f. - The prompt's `.strip()` took off every kind of whitespace, not only the spaces its docstring names: a tab, VT, FF, `\x1c` to `\x1f`, NEL, NBSP and U+3000. Typed, each gave `[ok] project Atlas` and exit 0, where `--project` refuses the same value as a control character or by the name pattern, and where #756's head refused it at the prompt too. It is now `.strip(" ")`. A typed line holds no newline here (`ask` drops it), so nothing else needs taking off. The test file's typed-refusal test takes the nine values, with stdin the terminal and with stdin a pipe, and the argv refusals take them as `--project`. The same reason is given on every way in. Each control character is typed after ^V, so the terminal takes it as text (`\x1c` alone is the quit key). - The name sentence said "letters" and "spaces", but `NAME_RE` takes only ASCII letters and U+0020, so its words described `Caf\xe9` and a no-break space as values that pass. Both sentences now say "an ASCII letter or digit first, then ASCII letters, digits, the space and ..." (the ref sentence without the space), as `adopt-project.py`'s help says "ASCII letters, digits and ...". The drift guards also walk every character above ASCII and find none taken. README, the handbook, AGENTS.md and docs/cli.md do not state either sentence (`render-cli-reference.py --check` is ok). - A ref is refused for `.lock` only where it ends in it: a new test passes `topic.locked` and `a.lockb` through `checked_ref`. - `a_run_for`'s list comprehension that only copied is gone. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is appended through `appended_to_command_scope`, as #848 asks of every hand-worked count, so the scan in `test_no_auto_maintenance.py` passes again (#777) `gh_that_is_signed_in` worked its `GIT_CONFIG_COUNT` out by hand from the environment's own count (`held + 1`, `GIT_CONFIG_KEY_{held}` and `GIT_CONFIG_VALUE_{held}`). That sum was right, and #848's scan (`test_no_test_sets_a_git_config_count_of_its_own`) refuses it in a test file now: it named `test_setup_project_name_edges.py:190`. #848 landed first, and its second-lander note gives this conversion to whichever of #848 and #811 lands second. The three entries now come from `appended_to_command_scope(os.environ, ...)`, merged into the returned dict beside `PATH`. The same three variables get the same values, under an environment that holds two entries, none, or an empty count. The docstring names the helper. No other line of the file changes. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…light's offers and `--shape-ref` is held to git's rule part by part, so a rehearsal no longer writes git's global config and `a.lock/b` is refused before the clone (#857) A `--dry-run` passed through after `--` ends the run at the plan, and the help and the plan's last line say nothing was created. The preflight before it still offered, on a terminal, every command that fixes what it found missing: an install of git or gh, `gh auth login` and `gh auth setup-git`. Each changes the machine on a typed `yes` (software, a gh login, a helper in the person's global git config), and a rehearsal that typed only the project name waited at the question until it was killed, as five of #777's tests did on CI. `preflight` now passes `offering` False down to `offer` for such a run, and `offer` says `not offered: --dry-run was passed through. A run without it offers this here, and so does --preflight.` where it would have asked, and asks nothing. Every check still runs and reports what is missing. The plan's last line adds that the preflight made none of its offers. `--preflight` still makes every offer, whatever follows `--`: it is the run that exists for them (#59). The help says so, and docs/cli.md is regenerated. `checked_ref` asked only `..` and a `.lock` at the very end, where git's rule is per part: `why_not_a_ref` refuses an empty part (`//`, a trailing `/`), a part that starts with `.`, a part that ends in `.lock`, and a `.` at the end, each in a sentence of its own. `a.lock/b`, `x/y.lock/z`, `release.lock/v1`, `a/.b`, `a//b`, `a/b/`, `a.lock/` and `a.` are refused before the standard is cloned; `topic.locked` and `a./b` go on. Over the 3110 names of up to five parts from {a, 0, ., /, -, .lock} that `REF_RE` takes, the tool and `git check-ref-format --branch` now give one answer, under git 2.43.0 and 2.47.1 (1122 disagreed before). New tests: tests/test_setup_project_dry_run_offers.py (each offer in process; a terminal run under a stub gh that leaves the global git config's bytes as they were, and one that leaves gh signed out) and tests/test_setup_project_ref_parts.py (the issue's ten names and nine more, each beside git, and three refused before a self-bootstrap clone). Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-18T03:53Z) @codex review |
Reviewer's GuideThe PR makes passed-through --dry-run a non-mutating rehearsal by suppressing all preflight offers while retaining --preflight behavior, and strengthens --shape-ref validation to agree with git part by part before repository setup begins. Sequence diagram for non-mutating dry-run preflightsequenceDiagram
participant User
participant SetupProject
participant Preflight
participant Offer
participant GitConfig
User->>SetupProject: run -- --dry-run
SetupProject->>Preflight: preflight(opts)
Preflight->>Preflight: passes_a_dry_run(opts.passthrough)
Preflight->>Offer: offer(command, offering=false)
Offer-->>Preflight: print NOT_OFFERED, do not prompt
Preflight->>Offer: offer(gh auth login, offering=false)
Offer-->>Preflight: print NOT_OFFERED, do not run
Preflight->>Offer: offer(gh auth setup-git, offering=false)
Offer-->>GitConfig: no global config change
Preflight-->>SetupProject: report prerequisites and continue
SetupProject-->>User: plan, nothing created or cloned
Sequence diagram for preflight offerssequenceDiagram
participant User
participant SetupProject
participant Preflight
participant Offer
participant GitHubCLI
participant GitConfig
User->>SetupProject: run --preflight
SetupProject->>Preflight: preflight_only(opts)
Preflight->>Offer: offer(gh auth login, offering=true)
Offer->>User: prompt for yes
User-->>Offer: yes
Offer->>GitHubCLI: gh auth login
Preflight->>Offer: offer(gh auth setup-git, offering=true)
Offer->>User: prompt for yes
User-->>Offer: yes
Offer->>GitHubCLI: gh auth setup-git
GitHubCLI->>GitConfig: write credential helper
Preflight-->>SetupProject: return after checks
Sequence diagram for rejecting invalid shape-ref before clonesequenceDiagram
participant User
participant SetupProject
participant CheckedRef
participant Git
User->>SetupProject: run --shape-ref a.lock/b
SetupProject->>CheckedRef: checked_ref(--shape-ref, a.lock/b)
CheckedRef->>CheckedRef: why_not_a_ref(a.lock/b)
CheckedRef-->>SetupProject: WHY_LOCK_PART
SetupProject-->>User: refuse value with reason
Note over Git: clone_and_bootstrap is not called
Flow diagram for git-compatible shape-ref validationflowchart TD
A[--shape-ref value] --> B[checked_ref]
B --> C[checked_value with REF_RE]
C --> D[why_not_a_ref]
D --> E{Git rule checks}
E -->|empty part| F[refuse with WHY_EMPTY_PART]
E -->|part starts with dot| G[refuse with WHY_DOT_PART]
E -->|part ends in .lock| H[refuse with WHY_LOCK_PART]
E -->|whole contains ..| I[refuse with WHY_RANGE]
E -->|whole ends in .lock| J[refuse with WHY_LOCK]
E -->|whole ends in dot| K[refuse with WHY_DOT_END]
E -->|valid| L[continue to repository setup]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79a33f112d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| why = why_not_a_ref(text) | ||
| if why: | ||
| refuse_value(flag, text, why) |
There was a problem hiding this comment.
Apply the ref-part validation in the bootstrap shim
When an invalid ref such as a.lock/b is supplied through the documented curl … setup.sh entry point, this new validation is reached only after setup.sh has cloned the repository and attempted the checkout. The shim's guard at setup.sh:235-268 still accepts these values, so the run performs the clone and terminates with Git's raw pathspec error instead of the promised pre-clone refusal; update the shim's mirrored check as well.
AGENTS.md reference: AGENTS.md:L55-L61
Useful? React with 👍 / 👎.
|
(lane openRepoShape-5, 2026-10-18T04:18:25Z) Independent review of #873 (#857) at No MUST findings. Both lines work as the body says, and every sentence the PR adds about a dry run is true. There is one test hole worth a short fix (S1). The setup.sh rule (S2) belongs in its own issue. Findings, most severe firstS1. SHOULD: the table never checks a dot-starting part anywhere but last.
S2. SHOULD, as its own issue and not in this PR: the old ref rule lives on in three bash copies.
N1. NIT: nothing tests that the dry-run check comes after
N2. NIT: the plan's new sentence prints even when nothing is marked.
N3. NIT: nothing tests the check order the docstring claims.
N4. NIT: nothing tests the short spellings of
Checked and true1. Line 1, measured in my own pty probe.
2. Line 2,
3. Fail on base: with the two new files copied onto 4. Mutants: I made 14 of my own (R1 to R14). 9 were caught (R2, R5, R6, R8 to R12, R14) and 5 survived: R1, R3, R4, R7 and R13, above. 5. Runs at
6. The restack's view.
7. setup.sh's copy of the old rule: confirmed, along with two more copies (S2). 8. Sonar-style checks:
Could not verify
Scratch left, and processes
Next, from lane 5: a small round for S1 and NITs 1, 3 and 4, then rv873 re-checks it. S2 goes to its own v1.1.1 issue. Lane: openreposhape-5 (openRepoShape-5) |
…y_run_offers.py: a part that starts with `.` is held anywhere in a ref, a name that breaks two rules is told the first, and a dry run with no terminal and one spelled `--dry` are held, so five mutants of #857's fix that passed every test are caught (#857) An independent review of #873 made 14 mutants of the fix; five survived the two new files and the setup tests. This closes each one with a test. - `a/.b/c` and `a/b/.c/d` join the table. The only dot-part rows put the part last, so a check of `parts[-1]` alone (R4) and of `parts[:2]` alone (R13) passed, and `git check-ref-format --branch` refuses both names. - Three names that break two rules hold `why_not_a_ref`'s order, which its docstring claims: `a//..b` is a range and not an empty part (R3), `a//.b` an empty part and not a dot part, and `a.lock/b.` a lock part and not a `.` at the end. With `a/.lock` and `a/b.lock` that holds all five pairs of neighbours. - A dry run with no terminal prints no `not offered` line and reads as it does without `--dry-run`, on each of the three machines (R1, which puts the dry-run check before `can_ask()`). - The in-process dry-run test and one terminal run take `--dry`, the short spelling `passes_a_dry_run` reads, so `preflight` cannot test the literal `--dry-run` (R7). setup-project.py is unchanged. The plan's last line still prints where nothing was marked `not offered`: telling the plan whether the preflight marked one takes a count written by `offer`, four calls below `preflight`, and the line is true as written. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-18T04:31:48Z) Fix round 1 of #873 (#857): One new commit on S1: "the table in
NIT 1: "nothing tests that the dry-run check comes after
NIT 3: "nothing tests the check order the docstring claims". Done, with two more rows than the review asked for.
NIT 4: "nothing tests the short spellings of
NIT 2 (optional): "the plan's new sentence prints even when nothing is marked". Left, as the brief allows.
S2 ( EvidenceMutants, at
To check that my rebuilt R-mutants are the review's, each was also run against copies of the two files as pushed at Runs at
Merge check: Hygiene: Note on the commit message: it says Next, from lane 5: rv873 re-checks this round. S2 is filed as #874. Lane: openreposhape-5 (openRepoShape-5) |
|
(lane openRepoShape-5, 2026-10-18T04:37:03Z) Re-check of #873 (#857) fix round 1 at All five mutants that survived my review are now caught, the runs pass, and the merge with #811's head is clean. There are no new findings. FindingsNone. NIT 2, left unfixed: the reason holds. The plan's last line prints even when nothing above is marked
A wording-only change, such as adding "if any", would avoid both. It stays optional and does not block. A small error in a commit message, no action needed. The fix commit's message says Checked and true1. Scope.
2. Each point is closed.
3. Mutants. I reran my own five with my
The tree read clean again after the runs. 4. Runs at
5. Merge. Other checks.
Could not verify
Scratch left
From lane 5: #873 is READY at Lane: openreposhape-5 (openRepoShape-5) |
…le's sentence says ASCII letters as setup-project.py's does, three rows hold the patterns off a part that starts upper-case, `_` or `-`, and two comments say what is true (#874) rv874's review of #875 (READY, no MUST), fix round 1. The shared sentence said "a letter or digit" and "letters", which `Café` satisfies under a UTF-8 locale although the patterns refuse it; #873's REF_RE words say "an ASCII letter or digit" and "ASCII letters" (#777, rv811). The three copies' refusals and `REF_RULE_WORDS` now say the same, byte for byte, and so does setup.sh's comment. `REF_PARTS` had no part that starts upper-case, `_` or `-`, so three over-refusing mutants passed all 33 rows of their copy's table test: `*/[-_]*` in setup.sh, `*/[A-Z]*` in install.sh and `[!a-z0-9]*` for `[!A-Za-z0-9]*` in openRepoShape. `Feature/ABC-123`, `fix/_tmp` and `fix/-wip` join the names that must pass, and each mutant is killed by them. `a/b.` and `a/.` move up to the names the old line took, where the docstring already counted them, from "refused before #874 as well". setup.sh's comment names `why_not_a_ref` as #857's, held here since #874, and says each rule has one or two patterns (an empty part and a `.lock` part have two). install.sh's note says a refused version is refused before the launcher is fetched: with no version named, `releases/latest` is asked first. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 3 days and 9 hours by commenting |
…and its round 2, and run to main 33c5128 (#883) Round 2b of #883. It answers RV883's full NIT list on ede678c (lane 3, 2026-10-18T09:29:28Z) and RV883's round 2 on 86baec5 (#883 issuecomment-6248362246). Each finding was checked against the code at main 33c5128 before the text changed: - The #843 limit is two: what `adopt-work-dir-shared` leaves open (group write, Windows, root as the other user, this user's own processes, the default work directory), and what it now refuses that v1.1.0 used (another user's directory, a run as root included, a 0777 directory without the sticky bit, a drive mounted without ownership), with the way out. "What a script sees" names `adopt-work-dir-shared` and `adopt-work-dir-occupied`. - The doctor's NEXT lines, not its `members` row, read no url from an unmerged `.gitmodules`; the `make siblings` limit names that row. `siblings.py` has no unmerged rule; the `validate.yml` templates do. - `validate-family.py`'s `family ok` is scoped to `--no-members` and to a holder whose members are checked out. - The wording points: `update-pr-no-repository` under `--pr`, family.py's `git rm -r --cached` line, `MSYSTEM` set and not empty, `..` and `naming-unclassified`, the keys git refuses with no value, the url the root's config keeps, `contracts: cannot be listed`, install.sh's newest tag and redirect location, the scan test's name, `update-rewrite-symlink`, `os.access` for root, `LC_ALL=C`, `update-validators-red`, `heads/main` in both keys, where `plan written to` came, junctions on Python 3.12, the approval click and the run's approvals, and the back-port route's one run. - #457's L1-17 is narrowed by #825; #853 and #854 join #834 as test limits. The refresh past c5359df adds #870 (#864), #876 (#839), #791 (#787), #859 (#838), #780 (#776), #873 (#857) and #875 (#874): 57 pull requests. It drops the limits for #776, #839 and #874's ref rule, rewrites the #838 clauses, and adds the limits those squashes name (#863, #879, #884, an older git without `GIT_NO_LAZY_FETCH`, family.py's link guard). The open list is the 14 that the 09:42Z milestone read leaves after #875: #675, #707, #739, #755, #764, #834, #840, #853, #854, #856, #863, #878, #879 and #884. The six-and-six shape-copy lists stand: from c5359df to 33c5128 the templates and repo_shape.py changed only in the family root's bootstrap.py and validate-family.py, both listed. No other file changes. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #857 (comment)
Stacked on #811 (
fix/777-typed-name-edges); retarget to main once it lands.#756 has landed as
4dea85ee, and #811's head is nowb6f56cad: #756's squash merged in, and a test-only change totests/test_setup_project_name_edges.py. This branch stays on050abea6, the merge base of the two, so its diff against #811's branch is its two commits: the fix (79a33f11) and fix round 1 (ac570069), which changes two test files and nothing else. Lane 5 restacks it after #811 lands.Line 1: a passed-through
--dry-runmakes none of the preflight's offersDirection: the silent reading. A run passed
-- --dry-runreports what is missing and asks nothing that would change the machine. Where a terminal would have been offered a command, it is toldnot offered: --dry-run was passed through. A run without it offers this here, and so does --preflight., and asked nothing. The plan's last line now ends:The preflight made none of its offers; a run without --dry-run makes each one marked `not offered` above.--preflightstill makes every offer, whatever follows--: it is the run that exists for them (#59), and it returns before anything reads the passthrough.Why. Measured at
050abea6, what each offer does on a typedyes, and all three change the machine:offer_missing): git or gh, through the package manager;gh auth login(_offer_login): gh's own sign-in;gh auth setup-git(_check_credential_helper): gh writes a credential helper into the person's global git config.A pty run of
setup-project.py Sample --org demoorg --elected-by Tester --into <dir> -- --dry-run, under a signed-in stubghand a git with no helper, askedRun `gh auth setup-git` now? Type yes to continue:. The typedyesran it, and the stub, which writes what the real one writes, added[credential "https://github.com"]to the fileGIT_CONFIG_GLOBALnames. The run then ended at the plan sayingnothing was created and nothing was cloned. With gh signed out, the same run askedRun `gh auth login` now?, and theyessigned the stub in. The help said a--dry-runthere "ends this run at the plan, creating nothing". And #811's CI run 39027235147 showed five rehearsals that typed only the project name waiting at that question until they were killed. Nothing argued for the offering reading: the offers stay one flag away (--preflight), and the real run makes them.What changed.
preflightpassesoffering = not passes_a_dry_run(opts.passthrough)topreflight_checks, and it goes down through_check_git,_check_gh,offer_missing,offer_install,_offer_loginand_check_credential_helpertooffer.offertests it aftercan_ask(), so a run with no terminal prints what it printed before, and it is a bool, neverOptions, sotest_the_offer_helpers_cannot_see_the_yes_flagstill holds. The help says it, anddocs/cli.mdis regenerated.AGENTS.md,README.mdand the handbook were grepped fordry-run,creating nothingand the offers: no sentence there is made false, so none is touched.The git install offer cannot be reached by a dry run end to end:
_mainrefuses a machine with no git (GIT_MISSING) before the preflight runs. It is held in process, throughpreflight, as the other two are.Tests (
tests/test_setup_project_dry_run_offers.py):test_a_dry_run_makes_none_of_the_offers_preflight_makes[install|login|helper], once for--dry-runand once for its short spelling--dry(passes_a_dry_runreads both), in process on every platform. A fake terminal typesyesto anything and a recorder runs nothing.preflightonSample --org demoorg -- <flag>makes no prompt and runs no offered command, saysnot offeredonce per offer (2, 1, 1), and refuses or passes as before.preflight_onlyon--preflight -- <flag>then makes the same 2, 1, 1 offers.test_a_dry_run_with_no_terminal_says_what_a_run_without_it_says[install|login|helper], in process: with no terminal (stdin is not one and there is none to open),preflighton-- --dry-runprints nonot offeredline, and what it prints is what the same run prints without--dry-run. It holds the order inoffer,can_ask()beforeoffering.test_a_dry_run_leaves_git_with_no_helper_as_it_found_it[py|piped|py with --dry], a pty run (PTY_SKIP). A signed-in stubgh, an empty system config and a global config of the run's own, with noGIT_CONFIG_COUNTand run from a folder that is no repository. The run asks nothing, exits 0 at the plan, prints the[??]warning with thenot offeredline under it and the new last line, never callsgh auth setup-git, and leaves the global config the bytes it was.test_a_dry_run_does_not_sign_gh_in: the same with gh signed out (git has a helper, so the login is the one offer at stake). It asks nothing, the[!!]line has thenot offeredline under it, it refusesone or more prerequisites are missing, and gh is still signed out.Line 2:
--shape-refis held to git's rule part by partDirection: finish the rule, in a new
why_not_a_refbesidewhy_refused, whichchecked_refasks afterREF_RE. Git's rule is per part: no part is empty (//, a trailing/), none starts with., none ends in.lock, and the whole holds no..and does not end in.. Each failure has its own sentence (WHY_EMPTY_PART,WHY_DOT_PART,WHY_LOCK_PART,WHY_DOT_END).WHY_RANGEandWHY_LOCKkeep #811's words, so itsref-rangeandref-lockcases are unchanged.REF_RE's comment andchecked_ref's docstring say what is asked.Evidence. At
050abea6,checked_refbesidegit check-ref-format --branchdisagreed on seven of the issue's ten names (a.lock/b,x/y.lock/z,release.lock/v1,a/.b,a//b,a/b/,a.lock/), and ona.anda/b., under git 2.43.0 and 2.47.1 alike. Over all 3110 names of up to five parts from {a,0,.,/,-,.lock} thatREF_REtakes, they disagreed on 1122 before and on none now, under both versions. In self-bootstrap mode--shape-ref a.lock/bcloned the standard and ended inerror: pathspec 'a.lock/b' did not match any file(s) known to git, exit 1. Now it is refused before(0) self-bootstrapprints.Tests (
tests/test_setup_project_ref_parts.py):test_the_tool_and_git_give_one_answer[<name>]: the issue's ten names and fourteen around them (a.,a/b.,a./b,a.lockb,a/b.lock/,a/.lock,a..b,refs/tags/v1.0,v1.0.0, and five from fix round 1). Each is fed tochecked_refand togit check-ref-format --branch, which must give one answer, and the tool's reason is pinned. No network. Fix round 1's five area/.b/canda/b/.c/d, a part that starts with.that is not the last part; and three names that break two rules, which hold the orderwhy_not_a_refasks them in (its docstring says the sentence names the first one the value failed):a//..bis a range, not an empty part;a//.ban empty part, not a dot part;a.lock/b.a lock part, not a.at the end. Witha/.lockanda/b.lock, that holds each of the five pairs of neighbours in the order.test_a_ref_git_refuses_is_refused_before_the_clone[a.lock/b|a/.b|a//b]: a self-bootstrap run exits 2 with the reason, prints nothing on stdout and clones nothing.Runs
Through
run9s.sh,SHARDS=3, at79a33f11: the two setup files, every other test file that namessetup-project(git grep -l 'setup-project' -- tests), the two new files and the trio, 25 files.Before the commit, unsharded on git 2.43.0:
test_setup_project_py.py,test_setup_project_name_edges.py,test_setup_project_typed_name.pyand the two new files, 377 passed.test_setup_sh.pyand the trio, 235 passed.Fix round 1, at
ac570069(setup-project.pyis unchanged, so every other file's result stands):SHARDS=3:test_setup_project_py.py,test_setup_project_name_edges.py,test_setup_project_typed_name.py, the two new files and the trio, 535 passed in 174.38s, rc 0.Fail on base
The two new files, run against
050abea6's code: atac570069, 40 tests, 28 failed and 12 passed (at79a33f11, 28 tests: 20 failed, 8 passed). All ten dry-run cases that make an offer fail: the three machines in both spellings, the three terminal runs and the signed-out run. Line 2's fifteen table rows that050abea6gets wrong fail: ten names git refuses that it passed,a/.lock, which it refused for the.lockand not for the part that starts with., and fix round 1'sa/.b/c,a/b/.c/d,a//.banda.lock/b.. So do the three self-bootstrap cases. The 12 that pass are the three no-terminal cases (050abea6made no offer there either), and nine table rows: eight where050abea6already agreed with git (a.lock,a/b.lock,topic.locked,a./b,a.lockb,a..b,refs/tags/v1.0,v1.0.0) anda//..b, which it refused for its.., with the sentence the order gives. They hold the table to both directions,topic.lockedpassing among them.Mutants
23 mutants, each run against its tests, and all 23 caught:
offerremoved;offeringnot passed on in each ofoffer_install,offer_missing,_check_git,_check_gh(install, login),_offer_login,_check_credential_helper, and each ofpreflight_checks's three calls;preflightalways offering;preflight_checkssilencing--preflight -- --dry-runtoo;not offeredwords changed;tests/test_cli_reference.py);checked_ref's old two checks put back (text.endswith(".lock"));why_not_a_ref's answer ignored;.lockpart sentence changed.The comment and docstring hunks have no mutant: they are prose. None survived.
An independent review then made 14 mutants of its own at
79a33f11. Nine were caught. Five survived the tests as first pushed (the two new files and the setup tests): R1, the dry-run check put beforecan_ask()inoffer; R3, the..and empty-part checks swapped; R4,parts[-1].startswith(".")only; R7, a literal"--dry-run" not in opts.passthroughinpreflight; and R13,parts[:2]only. Fix round 1 closes each, rebuilt from the review's descriptions and run atac570069against the two new files on git 2.43.0:a//..b(1);a/.b/canda/b/.c/d(2);--drycases in process and the--dryterminal run (4);a/b/.c/d(1).Run against the two files as first pushed (
79a33f11), the same five all survived, which is what the review found. Two more mutants for the new order rows are caught:a//.bfails with an empty part checked after a dot part, anda.lock/b.fails with a.at the end checked before a lock part. The other nine of the review's mutants were caught before and were not rerun.Merge checks
git merge-tree --write-treeof79a33f11, and ofac570069for the first line:Atlasno longer hides in step (4) (#777) #811's headb6f56cad: clean, for both (git merge-tree --write-tree b6f56cad ac570069, tree3e369754). Fix round 1 changes only the two test files this PR added, so the other lines below stand for it: they were checked at79a33f11and not run again.d7befb91: conflicts insetup-project.pyandtests/test_setup_project_typed_name.py. The conflict is inherited:050abea6alone conflicts withd7befb91in the same two files (setup-project.py: a project name typed at the prompt is checked as--projectis and an empty line says no name was typed, so../x,a/b,..and--helpare refused by name, not by an--idhint, a help page or a probe write (#729) #756 landed as a squash, and setup-project.py: a refused value names the check it failed, a project name over 95 characters is refused before step (4), and the prompt takes the spaces off a typed name, soAtlasno longer hides in step (4) (#777) #811's branch held its history). A commit-tree prediction of79a33f11merged ontob6f56cadis clean againstd7befb91.--add-topicafter the repositories exist (#838) #859495d4b33(fix/838-github-name-limits): clean.setup-project.pyanddocs/cli.mdmerge by themselves, with this head and with the predicted restack.MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821e15c237b(fix/711-git-bash-dialect): clean,setup-project.pymerging by itself. The predicted restack conflicts with it only inREADME.md,docs/handbook.htmlandshape-doctor.py, whered7befb91itself conflicts withe15c237b. None of the three is this branch's.fix/76405a1ebb3,fix/7962d51f181,fix/79870801c91andfix/80308f7a283(docs/cli.md): clean.fix/*orl3-*branch touches these four files.GIT_CONFIG_COUNTappends go throughappended_to_command_scope, the scan names a count worked out by hand, andpaste()gives a pastedsubmodule updatetheinsteadOfrewrite (#835) #848's scan on main (counts_set_to_a_number) finds no count set in either new test file.Left out
setup.sh's shim keeps its own copy of the old rule (*..*|*.lock), and its comment says it mirrorssetup-project.py. Undercat setup.sh | bash -s -- --shape-ref a.lock/bthe shim clones the standard and ends in git'spathspec 'a.lock/b' did not match, exit 1, before this file's check runs. Measured at this head, it is outside setup-project.py: a passed-through-- --dry-runstill offersgh auth setup-git, which writes the person's global git config on a typedyes, andchecked_refpassesa.lock/b, which git refuses #857's lines and is reported to lane 5 as a checklist candidate.not offered, where "each one markednot offeredabove" points at none. It is true as written. Printing it only where an offer was marked would take a count thatofferwrites and the plan reads, held in the module:offeris three to five calls belowpreflight, and each caller between returns a bool that the next one reads. That is state in a file whoseoffertakes its inputs as arguments and nothing else, for a sentence most dry runs would then not print, so it is left.Closes #857
Summary by Sourcery
Make dry-run rehearsals non-mutating and enforce Git-compatible shape references before any clone occurs.
Bug Fixes:
--dry-runexecutions from making preflight offers that could install software, authenticate GitHub CLI, or modify global Git configuration.--shape-refvalues that violate Git's per-component reference rules before cloning begins.Enhancements:
Documentation:
--preflightstill performs them.Tests:
git check-ref-formatand verify invalid references are rejected before self-bootstrap cloning.