Repository navigation
update-shape.py: a push or gh pr create that fails after apply --branch committed is named as that step, keeps the commit and ends on a NEXT line, and a re-run with --pr on the branch opens the missing pull request, so the tool no longer refuses "committing the re-synced shape" and then answers "nothing to do" (#204) - #227
Conversation
…ranch` committed is named as that step, keeps the commit and ends on a NEXT line, and a re-run with `--pr` on the branch opens the missing pull request, so the tool no longer refuses "committing the re-synced shape" and then answers "nothing to do" (#204) `_land` commits, pushes and opens the pull request. A `CommandFailed` from the push or from `gh pr create` used to reach `cmd_apply`'s one `except CommandFailed` arm, which named every failure "committing the re-synced shape", printed no NEXT line and did not flush stdout first. The run after it found the pin already at the target, said "nothing to do" and exited 0, so the tool never opened the pull request `--pr` asked for. The commit is now where rolling back stops: - `push_branch` and `open_pull_request` catch their own failure and hand it to `landing_failed`. The refusal names the step (`REFUSED pushing <branch>`, `REFUSED opening the pull request for <branch>`), says the commit stands and nothing was rolled back, and ends on a NEXT line. The exit stays 2. - The NEXT line is the `gh pr create` that `--pr` runs, title and body included, shell-quoted so it pastes as the same argv. `pull_request_line` builds it before the commit. When this clone's `origin/<branch>` is not HEAD, the line pushes first: `git -C <root> push -u origin <branch> && gh pr create ...`. Under `--push` without `--pr` it ends on the bare `gh pr create --base <tracking> --head <branch>`. - stdout is flushed before the refusal, so a combined log reads commit, push, REFUSED, NEXT, in that order. - `LandingIncomplete` carries the failure to `main` as exit 2. It is neither a `Refusal` (that arm rolls the working tree back under a commit holding those bytes) nor a `CommandFailed` (that arm names the commit). - A re-run with `--pr` and nothing to write finishes the landing when the checkout is on `--branch`, that branch is not the tracking branch, and its `contracts/shape-pin.yaml` differs from `origin/<tracking>`'s. `_finish_landing` asks `gh pr list --state all` before anything else: - an open pull request is reported, and no second one is opened; - a merged or closed one is reported, and nothing is pushed, so a merged branch the remote deleted stays deleted; - with none, `--push` pushes again and the same `gh pr create` runs. There is no second commit. - The refusal promises that re-run only under `--pr`, and names `apply --push --pr` when origin does not have the branch. - The `--pr` help says a later run on the branch opens a pull request that is still missing. docs/cli.md is regenerated. Unchanged, and measured byte-identical to main a5b09c3 in stdout, stderr, exit code and the `gh` argv log: - `check`; - `apply --branch`, and its re-run; - `apply --branch --push`, and its re-run; - `apply --branch --push --pr` with `gh` succeeding. A re-run without `--pr`, from a checkout not on `--branch`, with `--branch` naming the tracking branch, or on a branch whose pin is `origin/<tracking>`'s still prints "nothing to do" and exits 0, asking `gh` nothing and pushing nothing. Left out: the `--base main` that `next_line` prints after a successful `--branch` or `--branch --push` is #209's; a failure inside the commit, staging an `--add` path, is #202's; the adopt-side unflushed log is #188's. Lane: openreposhape-3 (openRepoShape-3) 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 1 day and 18 hours by commenting |
|
(lane openRepoShape-3, 2026-10-11T19:02Z) @codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Reviewer's GuideThe PR changes landing so commits remain intact when pushing or opening a pull request fails, reports the precise failed step with a pasteable recovery command, and allows a subsequent eligible --pr run to discover or create the missing pull request without recommitting; documentation and extensive regression tests cover the new flow. Sequence diagram for pull request recovery on rerunsequenceDiagram
participant User
participant Apply as cmd_apply
participant Check as _awaits_its_pull_request
participant Finish as _finish_landing
participant GH as gh
participant Land as _land
User->>Apply: rerun apply --branch --pr
Apply->>Check: _awaits_its_pull_request(...)
Check-->>Apply: eligible, nothing left to write
Apply->>Finish: _finish_landing(...)
Finish->>GH: gh pr list --state all --json state,url
alt Pull request is open
GH-->>Finish: OPEN
Finish-->>User: pull request is already open
else Pull request is merged or closed
GH-->>Finish: MERGED or CLOSED
Finish-->>User: no second pull request, no push
else Pull request is missing
GH-->>Finish: empty list
Finish->>Land: push_branch(...) if --push
Finish->>Land: open_pull_request(...)
Land->>GH: gh pr create ...
GH-->>User: pull request opened
end
Flow diagram for post-commit landing failuresflowchart TD
A[Commit re-synced shape] --> B{Push requested}
B -- Yes --> C[push_branch]
C -- Failure --> D[landing_failed]
C -- Success --> E{Pull request requested}
B -- No --> E
E -- Yes --> F[open_pull_request]
F -- Failure --> D
E -- No --> G[Landing complete]
F -- Success --> G
D --> H[Flush stdout]
H --> I[Print precise REFUSED step]
I --> J[Print pasteable NEXT command]
J --> K[Raise LandingIncomplete]
K --> L[main returns exit 2]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…servative alphabet and then `git check-ref-format --branch`, before anything is read, written or run, so a value that reads as an option never reaches `git`, `gh` or a NEXT line (#204) Fix round 1 for SonarCloud pythonsecurity:S8705, which flagged two places on PR #227 where `--branch` reaches a command with no validation it recognises: - `_git_answer`'s `subprocess.run`, reached from `landing_failed` on the re-run path, which never passes through the commit's own `checked_value`; - the printed NEXT line's `git -C <root> push -u origin <branch>`, which an agent pastes and runs. The fix: - `checked_branch` runs in `main`, before `cmd_apply`, whenever `--branch` is given. It refuses as `REFUSED update-branch-invalid`, with a remediation line, any value that: - `BRANCH_RE` does not fullmatch. That is the alphabet `checked_value` allowed, less `~`, with a letter or digit first, so there is no leading `-`, `/` or `.`; - `git check-ref-format --branch` then refuses, or would expand rather than echo. Git is asked only once the alphabet has passed. - `landing_failed` asks git which remote-tracking refs point at HEAD (`for-each-ref --points-at HEAD refs/remotes/origin/`) and looks for the branch in that answer, so the name is no longer a `git` argument there. - `--` ends git's options before the positionals: in `git push -q -u -- origin <branch>`, and in the NEXT line's `git -C <root> push -u -- origin <branch>`. - The two composite assertions Sonar also flagged (S9073) are split. Measured with the fixture from the first commit (Atlas from a5b09c3 plus upstream commit B), running `apply --branch=<value> --push --pr`: - On main a5b09c3 and on 6ed4e70, `-shape`, `--upload-pack=touch`, `shape/a b` and `shape/x@{1}` are refused `unsafe-value` only after the 12 lines of the rewrite. - `shape/a..b` and `shape/x.lock` fail as "REFUSED committing the re-synced shape" at `git checkout -b`, with the rewrite left modified in the tree. - `-shape` with nothing to do says "nothing to do" and exits 0. - On this head every one of them is refused `update-branch-invalid` with nothing on stdout and the tree untouched. - `shape/update-y` lands byte-identical to main: stdout, stderr, exit code, the `gh` argv log and the resulting refs. `--help` is unchanged, so docs/cli.md is too. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-11T19:19Z) fix round 1: SonarCloud S8705 (2 findings at update-shape.py:957 and :2002) fixed in 8705b27 by checking |
|
(lane openRepoShape-2, 2026-10-11T19:29:51Z) This lane reviewed PR #227 at 6ed4e70 read-only, in its own detached worktrees. It authored nothing and holds no claim. MERGEABLE WITH NITS. Whichever of #227 and #218 lands second changes one test call. #233 conflicts in 3 hunks, and keeping both sides resolves them. The branch has since moved to 8705b27, which was not reviewed. The issue's two runs are fixed. Head prints Findings (update-shape.py at 6ed4e70):
Tests:
Merges with 6ed4e70:
The full report is |
…y --branch` committed is named as that step with exit 127 and the NEXT line, on the first run and on the `--pr` re-run, so a machine without `gh` no longer ends in a `FileNotFoundError` traceback, exit 1, after the commit and the push (#204) Fix round 2, from the independent review (rv-update-shape) of 8705b27. - `_landing_run` wraps `run` for the three steps after the commit: the push, the re-run's `gh pr list` and `gh pr create`. `subprocess` raises `OSError` for a program it cannot start rather than returning an exit code, so it escaped every arm. It now becomes `CommandFailed(command, root, 127, "`gh` could not be started (<strerror>): is it installed and on PATH?")`, which `landing_failed` names as the step, keeping the commit, and ends on the NEXT line: the `gh pr create` to run once `gh` is installed. - Measured with PATH=/usr/bin:/bin (git and python3, no gh), `apply --branch shape/update-y --push --pr`, then the same command again: - main a5b09c3: exit 1 `FileNotFoundError: ... 'gh'` after "committed on" and "pushed"; the re-run says "nothing to do", exit 0. - 8705b27: the same first run; the re-run is exit 1 with the same traceback. - this head: `REFUSED opening the pull request for shape/update-y`, exit 127 in the block, NEXT `gh pr create --repo testorg/Atlas --base main --head shape/update-y --title …`, exit 2. The re-run is `REFUSED asking gh for shape/update-y's pull request`, with the same NEXT line, exit 2, and still one commit. - Tests: - `test_a_gh_missing_from_path_is_the_step_that_failed_on_both_runs` runs with a PATH of git's and Python's directories, and skips where `gh` lives beside one of them (Linux runners); - `test_a_gh_that_cannot_be_started_in_process` covers both runs on every platform; - `test_a_bad_branch_is_refused_even_with_nothing_to_write` pins fix round 1's one behaviour change. - Removing the `OSError` arm fails the two `gh` tests. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-11T19:43Z) fix round 2 (independent review, codex is out of quota): a |
…reate-failure origin/main 39ac59c carries #233 (#202, 150c41d), whose ledger rolls back a branch, a stage or a commit that fails and closes at the commit. Three hunks in update-shape.py, both sides kept: - EXIT CODES, apply 2: one sentence covering the rolled-back branch, stage or commit and the push or pull request kept after the commit (#204). - `_land`'s docstring: #202's ledger sentence, then #204's paragraph. - #202's `_rolled_back`, then #204's `LandingIncomplete` ... `pull_request_target`; only #202's old `open_pull_request` header goes. `_rolled_back`'s sentence saying a post-commit push or pull request "is #204's, and prints the line it always printed" now says it never reaches that arm: `LandingIncomplete` is neither of its two classes. tests/test_update_shape_add_ignored.py: the `branch-invalid` FAILURES case (`shape/update..ignored` failing `git checkout -b` after the writes) is now refused by `checked_branch` in `main` before anything is written, so it becomes its own test asserting `update-branch-invalid`, empty stdout and an untouched tree with the operator's staged work; `branch-df` still covers a branch-creation failure after the writes. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s rolled back as #202 rolls it back, and the same command then lands and opens the pull request on one commit After merging main (#233, #202), the commit is where rolling back stops from both sides: #202's ledger puts back a branch, a stage or a commit that fails and closes at the commit, and #204 keeps the commit when a push or `gh pr create` fails after it. The post-commit half was already pinned on the merged tree by the landing tests (`the_commit_stands`: clean tree, HEAD on the branch with the re-pin) and by #202's `test_a_failure_after_the_commit_lands_rolls_nothing_back`. Nothing drove the pre-commit half with the landing flags passed. Two tests do now: - `test_a_failure_before_the_commit_under_push_and_pr_is_rolled_back` (every platform; `gh` is never reached): a pre-commit hook refuses the commit under `--branch --push --pr`. Exit 2, `REFUSED committing the re-synced shape on <branch>`, #202's ROLLED BACK block (pin, manifest and copy restored, HEAD switched back, the branch deleted), and nothing of the landing: no push, no `gh` call, no NEXT line, no re-run promised, origin without the branch, the tree and refs as they were. - `test_the_same_command_after_a_rolled_back_commit_lands_and_opens` (shell `gh` stub, WINDOWS_SKIP): with the hook gone the same command has work to write, so it is the first run again, not the `--pr` re-run: one commit, pushed, one `gh pr create` and no `gh pr list`. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-11T22:05:55Z) Landing note: our #233 (#202) landed as 150c41d, and main is now 39ac59c. The "#233: the second to land resolves it like this" section of this PR's body applies as written. We applied its steps 1-5 on 39ac59c + 60038cc, then ran Our own 19:42Z note on #204 was incomplete. It missed two things:
Follow your PR body, not that note. The patch against this branch is on Eagle (py-bench) at |
|
(lane openRepoShape-3, 2026-10-11T22:21Z) fix round 3 (independent review, codex is out of quota): main 39ac59c is merged in as be70651; it carries #224 and #233 (#202). The 3
Lane 4's independent resolution (22:06Z note, its patch) gives the same code and the same Semantics checked. Re-measured on main 39ac59c: Targeted set (update_shape, _add, _add_ignored, _landing, shape_doctor, repo_hygiene, cli_reference) on c96396b: |
…reate-failure origin/main 2727b08 carries #218 (#209), plus 06b5204 (#214) and 60d08a1 (#217). No textual conflict: #218's `pull_request_repository` lands in `pull_request_target`, where this branch moved the manifest reading `open_pull_request` used to do, so `--pr`, the failure NEXT line and the re-run's `gh pr list` all read the one repository and base. #218's `test_a_manifest_naming_no_repository_refuses_pr_and_leaves_the_placeholder` called the old five-argument `open_pull_request`, a TypeError here; it now calls `update_shape.pull_request_target(tmp_path, kind)`, which is where `--pr` refuses, before the commit. #218's docstrings that named `open_pull_request` as the reader of the manifest name `pull_request_target`. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the repository with #218's `--repo`, so every `gh pr create` this tool prints or runs says which repository it opens against With #218 (#209) merged, `next_line`'s `--branch` and `--push` lines carry `--repo <the manifest's repository>`, and `--pr` always did. The one left was `pull_request_line`'s bare form, which is the `gh pr create` at the end of the NEXT line a failed push prints under `--push` without `--pr` (`git -C <root> push -u -- origin <branch> && gh pr create --base <tracking> --head <branch>`). Pasted from another directory, its `gh` opened against whatever repository the shell was in. - `pull_request_line` without `--pr` reads the manifest once and builds `gh pr create --repo <repository> --base <tracking> --head <branch>` through #218's `pull_request_repository`, the same answer `--pr` and `next_line` use. - A manifest naming no repository leaves `--repo` off rather than refusing: without `--pr` nothing is opened, and `next_line`'s `<org>/<Project>` placeholder would be one literal argument in a line quoted to be pasted as an argv. - Tests: the landing `failed_push[push]` case asserts `--repo testorg/Atlas`; #218's repository test checks both `pull_request_line` forms for a project and both family spellings; its no-repository test checks the bare form leaves `--repo` off. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-11T23:14Z) fix round 4 (independent review, codex is out of quota): this is the #218/#227 pair, since #218 landed first as 2727b08. Merge, 87ba8c5. Main 2727b08 (#218, #230, #246) merged with no conflict. #218's Round 4, c874e4f. Pair change (ii): Measured. Re-measured on main 2727b08: Tests. Targeted set (update_shape, _add, _add_ignored, _landing, _pr_base, shape_doctor, repo_hygiene, cli_reference) on c874e4f: Not chased. Main moved to 4b2add8 (#257, |
|
… via #236) into fix/adopt-record-submodule-placement Brings in b30595c (#249, #250), be2790e (#204) and 6d8d88b (#203) on top of c3a9645. No rebase. b30595c changes scripts/repo_shape.py and scripts/shape_materialize.py, as this branch does. be2790e changes update-shape.py and 6d8d88b changes scripts/family.py, neither of which this branch touches. One conflict, in scripts/shape_materialize.py's `from repo_shape import` block. b30595c imports `double_quoted` for `render`, and this branch imports `PATH_CLIMBS`, `PATH_ITSELF`, `PATH_NOT_CANONICAL` and `path_spelling_fault` for `_spelling_reason` and `LEG_SEGMENT_REASONS`. Kept all of them, in the block's order. Merged without a conflict, and checked: - scripts/repo_shape.py: b30595c reads a quote as opening a quoted scalar only where a scalar begins, and requires the whole text to be that one scalar (`_unquoted`, `_quote_end`, `_quoted_scalar`; `_scalar` hands them any text that begins with a quote). The record writes every path and reason as one whole scalar through `double_quoted`. `double_quoted`, `_escaped`, `_ESCAPE_OF`, `_unescape`, `parse_yaml` and `path_spelling_fault` are unchanged by the merge. Through the merged reader, `submodule_record` then `parse_yaml`, path and resolution, there are 0 mismatches in each set: - 4,525 names, plus 35 more; - 240 aimed at #252 and #248; - 195 aimed at #249 and #250: `it's`, `a"b`, `"abc`, `a" b "c`, `a' b 'c`, `"a\"`, `a\`, `[it's, a]`, `note "[1]"`, `"Brett Heap`, `O'Brien & Co: #1 [draft] {x}`, and more. - scripts/shape_materialize.py: b30595c spells a YAML template's `"{{KEY}}"` value with `double_quoted` in `render`. The record is appended after `render` has run (`text += append.get(rel, "")` in `_materialize`), so no part of it passes through `render`. This branch's `_spelling_reason`, `LEG_SEGMENT_REASONS` and `leg_path_problem` are unchanged. - No top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py, shape-doctor.py, validate-manifest.py, update-shape.py or scripts/family.py. Each merged module's names are the base's plus what either side added. The only thing both sides changed is the import block above. - The branch's diff against main is still #189 alone: 12 files, +1670 -157, file by file the same as against c3a9645. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main brings two commits since b30595c: - be2790e (#204 via #227): update-shape.py names a push or `gh pr create` that fails after `apply --branch` committed, with a NEXT line, with docs/cli.md and its tests; - 6d8d88b (#203 via #236): family.py `add` asks every refusal before it mounts anything. Neither changes a file this branch changes, nor scripts/repo_shape.py or scripts/shape_materialize.py. Every file the merge changes against this branch is main's, and every file it changes against main is this branch's. `render-cli-reference.py --check` still finds docs/cli.md current. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s `_entry_findings` composed with #255's, and this PR's pair renamed `unwritable_findings`/`unwritable_remediation` #231 (6f45bf9) puts each entry's path first in `_entry_findings` and returns `unsafe_remediation` ahead of the rest; this branch appends each value `execute` writes into a file that no file can hold. The two hunks are its docstring's order sentence and its return. The sentence keeps main's order and appends this PR's clause; the return is `(findings + submodule_findings + unwritable_findings, unsafe_remediation + remediation + unwritable_remediation)`, so the paths still come first, as #196 needs. This PR's local pair `value_findings, value_remediation` is renamed `unwritable_findings, unwritable_remediation`. #261's branch (ca21b57) binds `value_findings, value_remediation` in the same function, from `_manifest_findings`: a resolution keeping both lines under one name would silently drop one PR's findings. With the rename it cannot. The rest of main (#254/#281, #238/#287, #203/#236, #204/#227, #249/#250, #241, #215 and the earlier merges) merges with no conflict. The resolution is the lane's re-verify one (`resolve-main.py`, 385 passed on 6f45bf9), rename aside. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#241, #249/#250, #204, #203 and #238 merge with no conflict, and a `.gitmodules` value git refuses is answered before #190's relative-url check, which reads no url from a file git cannot read Main since d94d155, the main GitHub's update-branch merged at 8ce5614: c2ff2b0 (#215 via #240), c3a9645 (#241 via #256), b30595c (#249/#250 via #284), be2790e (#204 via #227), 6d8d88b (#203 via #236) and ede15ce (#238 via #287). No conflict hunk. Three files were changed on both sides; `git merge` auto-merged each, and every hunk of each side lies apart from the other's. Each side's +/- lines are identical before and after the merge in all three (`git diff -U0` d94d155..8ce5614 against ede15ce..merge, and d94d155..ede15ce against 8ce5614..merge). Line numbers are the merged file's. adopt-project.py - main, c2ff2b0 (#215): 384-506, `SUBMODULE_TYPED_KEY_RE` to `_refused_value` and `_who_refuses_a_boolean`; 510-515 and 522-524, `_registrations`' docstring and its return of `_refused_value`'s words; 791-797, `Source.registered_submodules`' docstring; 2181-2185, `_where_git_fails`' docstring. - #190: 36-38 and 51-53, the module docstring; 102-103, the `repo_shape` import list; 334-339, `Registrations.url`; 2252-2501, `RELATIVE_URL` to `_relative_url_findings`; 2503-2548, `_submodule_findings` and `_entry_findings` with `local_remote_dir` and the NOTEs; 2608 and 2630, `cmd_check`; 3070-3092, `_repository_urls` and `_remote_urls`; 3111-3216, `_refuse_what_check_finds` and `_as_resolved_here`; 3443, `cmd_execute`. scripts/repo_shape.py - main, b30595c (#249/#250): 173-535, the YAML reader's quote boundaries, from the comment after `YamlError` to `_split_flow`. - #190: 1975-1985, `REMOTE_PORT_RE` and `REMOTE_HOST_ALIASES`; 2099-2116, `remote_key`'s docstring and body. tests/test_shape_doctor.py - #190: 2662-2693, `test_a_port_and_githubs_ssh_host_are_not_part_of_a_remotes_key`. - main, ede15ce (#238): 5209-5808, the validator-PYTHONPATH tests after `test_a_drifted_roots_next_command_runs_when_it_is_pasted`. Every other file is one side's blob: main's for the 17 main changed, and 8ce5614's for tests/test_adopt_relative_submodule_url.py and tests/test_adopt_submodules.py. The merge's diff against main is #190's five files, with the stat it had against d94d155 (1097+, 41-). No function or class is bound twice at the top level of adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py, shape-doctor.py, scripts/family.py or the three test files. The helpers c2ff2b0 moved into tests/conftest.py (`planned`, `findings`, ...) are plain functions, and tests/test_adopt_relative_submodule_url.py imports seven named helpers from conftest and defines its own `planned`. HOW #215 AND #190 MEET. `_submodule_findings` lists `submodule_plan_problems` first, which holds #215's `plan-gitmodules-unreadable`, then `_relative_url_findings`. Where git's submodule reader refuses a value of the source's `.gitmodules`, `registered_submodules` answers with git's words, a string, and `_kept_relative_urls` reads no url from a string. So a source with both problems gets #215's finding alone, and no NOTE either. #190's finding is not lost: once the value is fixed, `check` reports it, and `execute` refuses it through the same list. #190 adds lines only after #215's, so it can hide nothing of #215's. A relative url kept in a leg is always beside a gitlink kept in a leg, where `_where_git_fails` names a failure, so the unreadable finding is always printed whenever #190's would be skipped. Measured on this tree with a source holding `upstream/dependency`, `url = ../dependency` and `update = bogus`, with `.gitmodules` and `upstream/` kept in code for testorg: - `origin` in otherorg: `check` 1 with `plan-gitmodules-unreadable` alone; `execute --local-remote-dir` 2, `REFUSED plan-gitmodules-unreadable`, with no remotes directory made. With `update = checkout`: `check` 1, `plan-submodule-relative-url`. - `origin` in testorg: the same `check` 1 and `execute` 2, with no NOTE. With `update = checkout`: `check` 0 and the NOTE. - At 8ce5614, before #215, the first source was `plan-submodule-relative-url`. The second was `check` 0 with the NOTE, over a file `git fast-export` dies on. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…`update-shape.py` with is a Python stub the update's child starts through a `sitecustomize` hook rather than a `#!/bin/sh` file on PATH, so the eight tests that carried `WINDOWS_SKIP` run on Windows too and the `--pr` landing and re-run paths (#204) are run by all three CI jobs, not two (#334) (#336) #227 drove `update-shape.py apply --branch --push --pr` against a `gh` that was a shell script on PATH, so every test that reached it was skipped on Windows: `CreateProcess` reads no shebang. No file the suite can write is a `gh` there either: for a program name with no extension it appends `.exe`, so a `gh.cmd` beside a script is not what `gh` names, and the runner's real `gh.exe` further down PATH would be the one that ran. So the stub is no longer found by searching PATH. - The stub is a Python program (`GH_STUB`): it logs its argv to `GH_LOG`, one line per call and each argument ended by a unit separator, as bytes, so the log is the same on every OS; `pr list` answers from `GH_STATE/list`; `pr create` exits 97 with the same `HTTP 422` line while `GH_STATE/create-fails` exists, else prints the URL and records the pull request; anything else exits 64. - Python imports `sitecustomize` at start-up from `PYTHONPATH`, so the update's child interpreter is started with a `sitecustomize.py` (`GH_ON_PATH`) first on it. It does what PATH resolution would have done: a `subprocess.Popen` whose program is named `gh` is started as `python <the stub> <the same arguments>`. The stub is a real process, its exit code and streams cross real pipes and the arguments cross the OS's own command-line quoting; `shape_materialize.run` still builds the `CommandFailed` from that process, and the refusal and the NEXT line still name `gh`, not python. `git` is untouched, and with no `GH_STUB` in the environment the module does nothing. - The `gh` fixture's interface is unchanged (`env`, `calls`, `failing`, `answer`, `forget`); `env` carries `PYTHONPATH`, `GH_STUB`, `GH_LOG` and `GH_STATE` in place of a `PATH` entry. - The eight `@WINDOWS_SKIP` marks come off, nine test items: each was skipped for the stub, as the file's own docstring said, and the rest of what they do (`git`, a refusing commit hook, NEXT-line quoting) is also done by tests of this file that were never skipped on Windows. The `WINDOWS_SKIP` and `stat` imports go with them. - The two in-process tests keep their bodies. Their docstrings no longer call themselves the Windows job's coverage: the first is the same two runs with no process for `gh`, the second is the only coverage of a missing `gh` on a runner that has `gh` beside `git` (the subprocess test skips there). Unchanged: no `assert` line and no test name moves (`git diff -U0 | grep -E '^[-+]\s*(assert|def test_)'` is empty), so no assertion is weakened; the scripts are untouched. What this does not exercise: the operating system finding a `gh` by searching PATH. The old stub did on POSIX only, and a missing `gh` stays covered by `test_a_gh_missing_from_path_is_the_step_that_failed_on_both_runs` and the in-process test. Left out: six other files skip on Windows for the same `#!` `gh` stub (`test_free_plan_hint.py` four times, `test_neutral_pin_validator.py`, `test_scaffold_pin_and_reuse.py`, `test_scaffold_pin_collisions.py`, `test_scaffold_leg_paths.py`, `test_scaffold_leg_path_collisions.py`); `GH_STUB` and `GH_ON_PATH` could move to `tests/conftest.py` for them. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, #238, #196, #254, #242 and #262/#263 merge with three adopt-project.py hunks kept on both sides, and #262's `_CASE_ASIDE` import kept as the name's one binding origin/main moved b30595c -> 71b58a1: - be2790e (#204 via #227), update-shape.py; - 6d8d88b (#203 via #236), family.py; - ede15ce (#238 via #287), shape-doctor.py; - 6f45bf9 (#196 via #231): adopt-project.py `entry_path_problem`, scripts/shape_materialize.py; - b421e6b (#254 via #281): scripts/shape_materialize.py, bump-leg.py, update-shape.py; - c0ed615 (#242 via #308), family.py; - 71b58a1 (#262 and #263 via #313): adopt-project.py `_root_shape_findings` and scripts/shape_materialize.py `copy_in_the_way`. It fixes what this branch's pull request listed as follow-ups (i) and (ii): a source's own `shape/` where the shape writes its copy, and a plain FILE where the shape needs a directory. Conflicts, all in adopt-project.py, each resolved by keeping both sides: - The module docstring's `check` paragraph. 71b58a1 adds "no path kept in the root where the shape writes ..." before the submodule clause, and this branch adds the symlink clause after it. Both are kept, in that order. - `_entry_findings`' docstring. 6f45bf9 and 71b58a1 give the order "each entry's path, then coverage, then each entry's `leg:`, the two mount paths and what the root keeps where the shape writes, then the source's own submodules". This branch adds "then each symlink the plan keeps in the root that a shape file cannot be written through". - `_entry_findings`' return line. 6f45bf9 returns `unsafe_remediation + remediation`, and this branch appends `_root_symlink_findings(plan, source, tree)` to the findings. The merge returns both. Found after the textual merge, and resolved here so that no commit binds a name twice: 71b58a1 (through 65a9262) imports #198's phrase as `_CASE_ASIDE` in the "#172's shape half" import block, and b37159a had imported it on a line of its own. Main's import is kept; this branch's line and its five-line comment go. scripts/shape_materialize.py merges with no conflict. `place` gets 71b58a1's `copy_in_the_way` inside its collision block and this branch's `_refuse_unwritable` after it, as 71b58a1's message foresaw. Checked on the merged tree: - `_entry_findings` still hands `_leg_findings` the tree, and the symlink findings still come last in its list. - AST: no top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py or scripts/shape_materialize.py, and none is loaded but bound nowhere. Main changed the signature of no function this branch calls. - Both sides are intact by +/- line sets, apart from the three hunks and the import above. The next two commits fix what this merge breaks, each with a revert line. #262's rule and this branch's both name a symlink kept AT the copy under `shape/`. And where the shape's copy lands is now `copy_in_the_way`'s question, asked before this branch's own check of it. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orking tree holds what HEAD commits at the pin, the manifest and every pinned copy, and is refused as `update-rerun-uncommitted` otherwise, so an in-place `apply --at C` left uncommitted on a branch that re-pins B no longer gets B pushed under a pull request titled C (#333) (#379) `cmd_apply` decided "nothing to write" from the working tree (`read_pin`, `classify`), and `_awaits_its_pull_request` decided that a pull request was owed from HEAD's `contracts/shape-pin.yaml` against `origin/<base>`'s. Over a tree that is HEAD's that is one answer; with an uncommitted change in between it was two, and `_finish_landing` pushed HEAD under a pull request built from the working tree. Measured on ab623dd with local bare remotes and a logging `gh` shim: B committed and pushed on the branch, an in-place `apply --at C` left uncommitted, then `apply --at C --branch --push --pr` exited 0, said the pin on the branch already named C, pushed the branch (its pin B) and ran `gh pr create --title "Re-sync the shape copies to opensoft/openRepoShape @ <C>"`. - Once `_awaits_its_pull_request` recognises a re-run, and before `gh` is asked or anything is pushed, `_refuse_what_head_does_not_commit` compares the pin, the kind's manifest and every pinned copy with HEAD by bytes (`git hash-object` on the file against `HEAD:<path>`: the clean filter runs, and no index flag hides an edit) and refuses `update-rerun-uncommitted`, naming each path that differs or that HEAD does not commit. - The refusal names HEAD and the commit the pull request would be titled with and, where the two pins differ, both of them ("the pull request would name one commit and carry another"). Its remediation: commit those changes on the branch, or put them back to HEAD's bytes, then run the same command again. Nothing has been written, so nothing is rolled back. - The index is not asked, and neither is any other path (a README edit, an untracked file): nothing on this path commits, and neither decision reads them. Unchanged: a re-run over a clean tree, and every case tests/test_update_shape_landing.py and tests/test_update_shape_pr_base.py test (#227, #218); the first-run path and #202's rollback before the commit, which the new check is not on; `check`; every `--help` text. Upstream commit C, which changes `scripts/validate-pins.py` alone, is added to the landing tests' module fixture; no existing test reads the upstream's tip. The new tests run the update as a child with that file's Python `gh` stub (#334), so they run on all three operating systems and a `gh` the tool asked would be heard. They hold the comparison to both of its properties: the issue's case is still refused with its paths marked `--assume-unchanged` or `--skip-worktree`, which a `git diff HEAD` would not see, and a CRLF pin and manifest that the project's `* text=auto eol=lf` cleans to HEAD's blobs still open the pull request. Left out: the first-run path over an uncommitted in-place apply still commits only the paths it wrote, so `apply --at D --branch <new>` over an uncommitted `apply --at C` exits 0 with a commit whose own `validate-pins.py` is red (measured); a `--pr` run whose working tree needs a write HEAD already holds still ends in git's "nothing to commit", rolled back, rather than a named refusal; and `apply --at C --branch <new> --pr` over an uncommitted in-place C still says "nothing to do". Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #204 (comment)
Seven commits, to land as one squash. The final message is drafted by the lane:
applykeeps each of the manifest'sshape:pin lines at the indentation it already has and reads the rewrite back, so aproject.yamlindented four spaces takes the update instead of being rolled back under the validators' lockstep remedy (#208) #224 and update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233; the update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233 conflict is resolved as this body said it would be;--push --pr;apply --branchprints the root's own tracking branch as thegh pr create --baseand its assembly repository as the--repo, the two values--prpasses, so a project scaffolded with--tracking-branch trunkis no longer told to open its pull request againstmain, and the--pushline no longer prints a literal<org>/<Project>that a shell reads as a redirection (#209) #218, shape_materialize.run() and 26 other git and gh calls read what the child wrote as UTF-8 whatever the locale, so anadopt-project.py executewhose leg push is rejected with a message naming a non-ASCII path endsREFUSED pushing the spec legand the ruleset hint underLC_ALL=C PYTHONUTF8=0, not a UnicodeDecodeError traceback (#217) #230 and adopt-project.py and five other tools escape a name their stdout cannot encode, soplanon日本.mdunder a cp1252 stdout (strict, or Windows' default surrogateescape) no longer exits 1 after writing the plan (#214) #246; update-shape.pyapply --branchprints the root's own tracking branch as thegh pr create --baseand its assembly repository as the--repo, the two values--prpasses, so a project scaffolded with--tracking-branch trunkis no longer told to open its pull request againstmain, and the--pushline no longer prints a literal<org>/<Project>that a shell reads as a redirection (#209) #218's no-repository test now callspull_request_target;apply --branchprints the root's own tracking branch as thegh pr create --baseand its assembly repository as the--repo, the two values--prpasses, so a project scaffolded with--tracking-branch trunkis no longer told to open its pull request againstmain, and the--pushline no longer prints a literal<org>/<Project>that a shell reads as a redirection (#209) #218's--repoon thegh pr createa failed--pushprints.What
_landcommits, pushes and opens the pull request. A failed push orgh pr createused to reachcmd_apply's oneexcept CommandFailedarm. That arm called every failure "committing the re-synced shape", printed no NEXT line and did not flush stdout. A missingghescaped as a traceback. The re-run then said "nothing to do" and exited 0. The commit is now where rolling back stops:The failing step is named.
push_branchandopen_pull_requesthand their failure tolanding_failed, which printsREFUSED pushing <branch>orREFUSED opening the pull request for <branch>. It says the commit stands and nothing was rolled back, and ends on a NEXT line. The exit is 2.A program that cannot be started is that same failure (round 2).
_landing_runturnsOSError, such asghnot installed or not on PATH, into exit 127 with the message "ghcould not be started (…): is it installed and on PATH?".The NEXT line is the command that failed. It is the
gh pr createthat--prruns, title and body included, shell-quoted so it pastes back as the same argv.pull_request_linebuilds it before the commit.git -C <root> push -u -- origin <branch> && gh pr create ….for-each-ref --points-at HEAD refs/remotes/origin/, so the branch name is never agitargument there.--pushwithout--pr, it ends ongh pr create --repo <repository> --base <tracking> --head <branch>, the bare form update-shape.pyapply --branchprints the root's own tracking branch as thegh pr create --baseand its assembly repository as the--repo, the two values--prpasses, so a project scaffolded with--tracking-branch trunkis no longer told to open its pull request againstmain, and the--pushline no longer prints a literal<org>/<Project>that a shell reads as a redirection (#209) #218's--branchNEXT line prints (round 4). A manifest that names no repository leaves--repooff rather than printing a placeholder inside a line quoted as an argv.stdout is flushed before the refusal, so a combined log reads commit, push, REFUSED, NEXT.
LandingIncompletecarries the exit 2 tomain. It is neither of the two classes thatcmd_apply's arm hands to update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's_rolled_back. So a failure after the commit never undoes the commit thatledger.land()closed the ledger on, and the step is never renamed "committing".A failure before the commit is still update-shape.py:
apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's rollback, with--pushand--prpassed too (after the merge; tests in round 3). A branch, a stage or a commit that fails puts back the copies, the pin, the README line, the index and the branch, and names the step. Nothing of the landing happens: no push, noghcall, no NEXT line, no re-run promised.A re-run with
--prfinishes the landing. This applies when there is nothing to write, the checkout is on--branch, that branch is not the tracking branch, and itscontracts/shape-pin.yamldiffers fromorigin/<tracking>'s._finish_landingasksgh pr list --state allfirst:--pushpushes again and the samegh pr createruns. There is no second commit.The refusal promises that re-run only under
--pr. When origin does not have the branch, it namesapply --push --pr.--branchis checked once, inmain, before anything is read, written or run (round 1).checked_branchrefuses withREFUSED update-branch-invalidand a remediation line unless:BRANCH_REfullmatches the value:checked_value's alphabet less~, with a letter or digit first, so there is no leading-,+,/or.;git check-ref-format --branchthen echoes it unchanged.This happens whether or not there is anything to write.
git push -q -u -- origin <branch>:--ends git's options before the positionals.--prhelp: a later run on the branch opens the pull request if it is still missing.docs/cli.mdis regenerated.Why
The issue's repro, re-measured on this machine against main 2727b08, which carries #218. Atlas is scaffolded from a clone of main 39ac59c into local bare remotes, with one upstream commit B (
25ff23b96682); the tree under test is main 2727b08 against this branch at c874e4f.ghis a PATH shim that logs its argv and exits 97 onpr createwhile a marker file exists. Every run uses the same path and fixed git dates, so outputs compare byte for byte.--branch shape/update-y --push --pr,gh pr createfailsREFUSED committing the re-synced shape, no NEXT lineREFUSED opening the pull request for shape/update-y;…origin has it: that commit stands and nothing was rolled back; NEXT = thegh pr createthe shim loggedghsucceedsnothing to do, noghcallgh pr list, then the samegh pr create;pull request …/pull/7; 1 commitnothing to do…/pull/7 is already open;gh pr listonly> log 2>&1committed onon 21,pushedon 22committed on14,pushed15, REFUSED 16, NEXT 25--push --pr, origin refuses the pushREFUSED committing the re-synced shape, no NEXT lineREFUSED pushing shape/update-y; NEXTgit -C <root> push -u -- origin shape/update-y && gh pr create --repo testorg/Atlas …--pushwithout--pr, origin refuses the pushREFUSED committing the re-synced shape, no NEXT lineREFUSED pushing shape/update-y; NEXTgit -C <root> push -u -- origin shape/update-y && gh pr create --repo testorg/Atlas --base main --head shape/update-y; no re-run promisednothing to do; the branch never reaches originghreports the pull request MERGEDnothing to do…/pull/5 for shape/update-y is merged; a re-run opens no second one;gh pr listonly, no push--prwithout--push,gh pr createfailsREFUSED opening the pull request …; NEXT pushes first; promisesapply --push --prPATH=/usr/bin:/bin(git and python3, nogh),--push --prFileNotFoundError: … 'gh'after the commit and the pushREFUSED opening the pull request for shape/update-y,exit 127; NEXTgh pr create --repo testorg/Atlas …ghnothing to doREFUSED asking gh for shape/update-y's pull request, exit 127, the same NEXT line, still 1 commit--branch=-shape,--upload-pack=touch,shape/a b,shape/x@{1}REFUSED unsafe-valueafter 12 lines of rewrite, rolled backREFUSED update-branch-invalid, nothing on stdout, tree untouched--branch=shape/a..b,shape/x.lockREFUSED creating the branch …after 12 lines of rewrite, rolled back by #233REFUSED update-branch-invalid(git's verdict quoted), nothing on stdout, tree untouched--branch=-shapewith nothing to donothing to doREFUSED update-branch-invalidcheck;--branchand its re-run;--branch --pushand its re-run;--branch --push --prwithghsucceeding;--branch=shape/update-y --push --prghargv log (cmp)--branch --push --pr, a pre-commit hook refuses; r10b the same command once the hook is goneREFUSED committing the re-synced shape on shape/update-y, then #233'sROLLED BACKblock; origin has no branch, onlymainis left, the tree is clean,ghis never called. r10b: exit 0, committed, pushed, onegh pr create, 1 commitTests
tests/test_update_shape_landing.pyhas 33 tests.Round 4 asserts
--repo testorg/Atlasin the landingfailed_push[push]line. It also checks bothpull_request_lineforms in #218's repository test (project, and both family spellings), and the bare form without--repoin #218's no-repository test. Run against the merge commit 87ba8c5, which does not have the change, these give4 failed, 8 passed: the three repository cases andfailed_push[push]fail, and the no-repository guard passes before and after. Its first 15 gave10 failed, 5 passedon main a5b09c3. The 5 that pass there pin behaviour that must not change.Round 3 adds two tests:
test_a_failure_before_the_commit_under_push_and_pr_is_rolled_backruns on every platform, becauseghis never reached.test_the_same_command_after_a_rolled_back_commit_lands_and_opensuses theghstub.In the merge, #233's
branch-invalidcase intests/test_update_shape_add_ignored.pybecomestest_a_branch_git_cannot_name_is_refused_before_any_write. That test assertsupdate-branch-invalid, empty stdout, and a tree untouched with the operator's staged work in it.Mutations, each run against the whole file:
--, the pushed-ref read);OSErrorarm fails both missing-ghtests;test_update_shape_add_ignored.py:LandingIncomplete). Result:13 failed, 12 landing tests plus update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233'stest_a_failure_after_the_commit_lands_rolls_nothing_back.--pushor--pris passed. Result:2 failed, 10 passed. Only the two new tests kill it; every one of update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's own cases survives it. Under M2 the re-run even says "nothing to do" over the uncommitted re-pin, which is update-shape.pyapply --addstages the new file with a plaingit add, so a path the operator's excludes ignore fails after the copies, the re-pin and the branch are written, nothing is rolled back, andcheckthen exits 0 over a re-pin nobody committed #202's original bug.Targeted set on
c874e4f71058bd6bba94ae100c92603ef8d4a6d1:tests/test_update_shape.py,tests/test_update_shape_add.py,tests/test_update_shape_add_ignored.py,tests/test_update_shape_landing.py,tests/test_update_shape_pr_base.py,tests/test_shape_doctor.py,tests/test_repo_hygiene.py,tests/test_cli_reference.py→424 passed in 839.27s (0:13:59). At c96396b, before #218, it was397 passed.scripts/render-cli-reference.py --check:[ok]. Merging main did not change any--helptext. pyflakes is clean onupdate-shape.pyand both test files.Base tested: 2727b08. Main has since moved to 4b2add8 (#257,
scripts/repo_shape.pyflow mappings and its own test);git merge-tree --write-tree 4b2add8 c874e4fis clean.On Windows:
WINDOWS_SKIP(8 functions, one parametrized twice), because they drive theghshell-script stub and CreateProcess reads no shebang.test_a_gh_missing_from_path_is_the_step_that_failed_on_both_runsskips whereverghlives beside git or Python, which is the case on the Linux runners (all three in /usr/bin). Elsewhere it runs.ghruns on every platform.--branchtests and the round-3 rollback test also run on every platform. The rollback test's hook is a shebang script, which Git for Windows runs through its ownsh, as update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's tests rely on.Composes with
fix/update-shape-manifest-indent(#208)fix/update-shape-add-rollback(#202, lane 4)fix/update-shape-pr-base-branch(#209)fix/decode-git-output-utf8(#217)runstill raisesOSErrorfor a program that cannot start, which_landing_runcatchesfix/update-shape-add-dangling-symlinkgit merge-tree --write-tree 7bceeca c874e4f)#233, as merged in be70651. These are the steps this body stated before #233 landed. I re-derived them against the landed 150c41d, which is much larger than the c022b64 they were first trialled on:
applyline: one sentence, "2 a REFUSAL, including a validator that went red or a branch, stage or commit that failed (rolled back); or a push or pull request failing after the commit, which is KEPT, and the NEXT line printed then finishes the job (update-shape.pyapply --branch --push --pr: whengh pr createfails after the commit and the push succeeded, it exits 2 with "REFUSED committing the re-synced shape" and no NEXT line, and a re-run on that branch says "nothing to do" and exits 0, so the tool never opens the pull request #204)"._landdocstring: both paragraphs, update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's ledger sentence first.apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's_rolled_back, then this branch'sLandingIncomplete…pull_request_targetblock. Only update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233's olddef open_pull_request(root, branch, target, upstream, kind)header is dropped._rolled_back: it now says that a push or pull request failing after the commit never reaches that arm.LandingIncompleteis neither of the arm's classes.tests/test_update_shape_add_ignored.py: thebranch-invalidentry leavesFAILURESand becomes its own test (see Tests).checked_branchrefusesshape/update..ignoredinmainbefore anything is written. update-shape.py:apply --addforce-stages its additions past the operator's own excludes, and a branch, stage or commit that fails before the commit lands now puts back the copies, the pin, the README line, the index and the branch and names the step, socheckno longer exits 0 over an uncommitted re-pin (#202) #233'sbranch-dfcase still covers a branch git cannot create after the writes.Lane 4 resolved the same merge independently (its landing note on this PR, 22:06Z). Its tree has the same code, and the same
_landdocstring, as be70651. The two differ in three places only: the line wrap in step 1, the wording in step 4, and that lane 4 deletes thebranch-invalidcase where be70651 keeps it as a test.#218, as composed in 87ba8c5 and c874e4f. #218's
pull_request_repositorymerged intopull_request_target, where this branch moved the manifest readingopen_pull_requestused to do. So--pr, the failure NEXT line and the re-run'sgh pr listall read one repository and one base. The two changes the second lander owed are made:test_a_manifest_naming_no_repository_refuses_pr_and_leaves_the_placeholdercallsupdate_shape.pull_request_target(tmp_path, kind)in place of the old five-argumentopen_pull_request(87ba8c5). update-shape.pyapply --branchprints the root's own tracking branch as thegh pr create --baseand its assembly repository as the--repo, the two values--prpasses, so a project scaffolded with--tracking-branch trunkis no longer told to open its pull request againstmain, and the--pushline no longer prints a literal<org>/<Project>that a shell reads as a redirection (#209) #218's docstrings that namedopen_pull_requestas the manifest's reader now namepull_request_target.pull_request_line's bare form carries--repo(c874e4f), so everygh pr createthis tool prints or runs names the repository.Left out
--prre-run over a working tree whosecontracts/shape-pin.yamlor manifest differs from HEAD's (an in-placeapply --at Con the branch) pushes HEAD and titles the pull request with C. It should require a clean pin and manifest, or refuse.shlex.join), which cmd.exe does not read.REFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188.Closes #204
🤖 Generated with Claude Code
Summary by Sourcery
Keep committed shape updates intact after landing failures and make subsequent pull-request completion safe and actionable.
New Features:
apply --prrun to complete a missing pull request after the branch commit has already been created.Bug Fixes:
ghinstallations as actionable landing failures instead of uncaught tracebacks.Enhancements:
Documentation:
Tests:
gh, retries, rollback behavior, branch validation, and cross-platform execution.