Repository navigation
update-shape.py: a --pr re-run finishes the landing only when the working 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
Conversation
…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) `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. 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>
|
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 7 hours and 52 minutes by commenting |
Reviewer's GuideThe PR closes the re-run inconsistency by validating that the working tree’s pin, manifest, and every pinned copy exactly match what HEAD commits before allowing a no-op pull-request landing to proceed; mismatches are named and refused before GitHub or push side effects, with focused regression coverage for the affected scenarios. Sequence diagram for validating a pull-request re-run before landingsequenceDiagram
participant User
participant Apply as cmd_apply
participant Rerun as _awaits_its_pull_request
participant Check as _refuse_what_head_does_not_commit
participant Git as Git
participant GH as gh
participant Remote as Git remote
User->>Apply: apply --branch --pr --push
Apply->>Rerun: _awaits_its_pull_request()
Rerun-->>Apply: re-run recognised
Apply->>Check: _refuse_what_head_does_not_commit()
Check->>Git: _not_what_head_commits()
Git-->>Check: compare working-tree hashes with HEAD blobs
alt pin, manifest and pinned copies match HEAD
Check-->>Apply: allow landing
Apply->>Remote: push
Apply->>GH: gh pr list / gh pr create
else any compared path differs
Check-->>User: Refusal update-rerun-uncommitted
Note over Check,GH: No push, GitHub request, or write occurs
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-12T14:50:34Z) @codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…ed with its uncommitted paths marked `--assume-unchanged` or `--skip-worktree`, and a CRLF pin and manifest that are otherwise HEAD's still open the pull request, so the two properties the byte comparison was chosen for are tested (#333) Independent review of PR #379 (round 1): `_not_what_head_commits` compares `git hash-object <file>` with `HEAD:<path>` because the clean filter runs and no index flag hides an edit, and no test held either property. With the comparison swapped for `git diff --quiet HEAD -- <path>`, every new test still passed while the issue's case, its paths marked `--assume-unchanged`, exited 0 and pushed B under a pull request titled C. - `test_a_rerun_over_a_tree_head_does_not_commit_is_refused_by_name` gains `assume-unchanged` and `skip-worktree`: the in-place `apply --at C` with the pin, `project.yaml` and `scripts/validate-pins.py` marked with that flag, so `git status --porcelain` is empty and `git diff --quiet HEAD` exits 0. Each is refused as the in-place-apply case is, naming the same three paths and both commits. - `test_a_crlf_pin_and_manifest_still_hold_what_head_commits`: the pin and `project.yaml` rewritten with CRLF line ends, which the project's `* text=auto eol=lf` cleans back to HEAD's blobs. The re-run opens the pull request for B as a clean one does, and leaves both files' bytes as they were. A CRLF pinned copy cannot show this, because it is `locally-modified` before the re-run is recognised. Both run the update as a child with the file's Python `gh` stub (#334), on every operating system, with no skip. Against the index-reading comparison the two new cases fail (exit 0, "pushed"); against `hash-object --no-filters` the CRLF test fails; on this commit all eight pass. update-shape.py is unchanged. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-12T15:36:05Z) fix round 1 (independent review, codex is out of quota): Finding (should-fix): no test held the property
Bite checks, with the tool mutated in a scratch worktree and the eight #333 tests run against it:
On 3465a6a: A correction to "Composes with" in the description: #380 (fix/pin-rewrite-indentation) at 73068a3 keeps |
|
…plan-manifest-invalid` joins `_entry_findings` after #255's values, and this branch's `plan-root-symlink` stays after the source's own submodules origin/main moved 92e29d9 -> dd8d0ea (twelve commits), merged by sha: - f0a8bf6 (#330 via #381), scripts/family.py; - 87d2eb7 (#333 via #379), update-shape.py; - cd1fc79 (#327 via #375), shape-doctor.py; - 45e01f4 (#348 via #396), tests/test_agents_md_codes.py: every code-shaped name AGENTS.md spells is a code a tool prints; - 3ef059c (#258 and #261 via #314), adopt-project.py, scaffold-project.py and scripts/shape_materialize.py: the project's YAML reader and its `validate-manifest.py` are asked about the manifest's values before anything exists; `check` reports `plan-manifest-invalid` through `_manifest_findings` in `_entry_findings`; `_repository_urls` refuses `adopt-name-too-long`; - f7f4b14 (#366 via #399), scripts/family.py; - cb7063f (#301 and #280 via #390), scripts/repo_shape.py and the template's validate-manifest.py: a bracket or brace in a plain flow scalar is refused; - 5678f68 (#278, #297 and #298 via #357), shape-doctor.py; - 53bee5b (#289 via #352), scripts/bump-leg.py; - f5cb616 (#369 via #398), scripts/shape_materialize.py: the docstrings of `force_stage` and `stage_written`; - a92e60b (#326 via #400), scripts/family.py; - dd8d0ea (#292 via #353), adopt-project.py: `_elector` keeps a `user.name` byte that is not UTF-8, `_remote_repository` reads an origin URL that is not UTF-8 as none, and `adopt-no-org` is asked before `checked_value`. scripts/shape_materialize.py, scripts/repo_shape.py and every other file merge with no conflict. adopt-project.py conflicts in two hunks of `_entry_findings`, each resolved by keeping both sides: 1. The docstring. Main's order, with #261's manifest findings after #255's values and before #190's relative urls; this branch's symlinks stay after the source's own submodules. 2. The return. Main's `_manifest_findings(plan)` call, and the findings in main's order: each entry's path (#196), coverage, the legs and what the root keeps where the shape writes, the source's own submodules (#166), this branch's `_root_symlink_findings`, #255's values, #261's manifest findings, and #190's relative urls last. The remediation and the NOTEs are main's. How each of main's commits that reach this branch's code meets it: - 3ef059c: `_manifest_findings` asks the validator the template ships about the values `execute` writes; it reads no symlink and no path the shape writes. Its materializer half adds the validator's helpers, `refuse_unstorable_names` and `MANIFEST_VALIDATOR`, and holds `election_date` to the validator's `DATE_RE`; it leaves `place`, `_materialize`, `copy_tree`, `template_files` and `_refuse_unwritable` as they were. In `execute`, its `adopt-name-too-long` (in `_repository_urls`) comes before #255's clone-URL refusal, `_refuse_what_check_finds` and this branch's `core.symlinks` probe, all before any leg. Its new paragraph of the module docstring follows the one 6b53a6d corrected; both merge with no conflict. - f5cb616: docstrings only. - dd8d0ea: `_remote_repository`, `_checked_plan_inputs`' organisation and `_elector`, none of them this branch's. - cb7063f: the plans `check` and `execute` read go through this reader. The plans `plan` writes for this branch's tests hold their entries as block mappings, each path `double_quoted` where it needs to be, and the rehearsal below read every one of them. Checked on the merged tree: - AST (fin183g/names.py, base 92e29d9): no top-level name is bound twice in adopt-project.py, scaffold-project.py or scripts/repo_shape.py, and none is loaded but bound nowhere. scripts/shape_materialize.py binds `YamlError` and `parse_yaml` twice, as main dd8d0ea does (1f90a0f's import and 3ef059c's, the same objects); the merge adds none. - Both sides intact (fin183g/sides_intact.py): the only lines that differ from either side's own change are those of the two hunks. - A rehearsal of this merge (tree d21f8b4, the same as this one) passed tests/test_adopt_root_symlinks.py with main's test_adopt_{manifest_values,git_reads,relative_submodule_url}.py, test_scaffold_manifest_values.py, test_agents_md_codes.py and test_yaml_flow_brackets.py: `440 passed`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t: `check_pin` takes the root its `update-pin-row` remediation reads, so #290's refusal survives #267's split instead of a NameError (#290) Main 580d7df carries #267 (decff87), which split update-shape.py's `read_pin(root)` into the read and `check_pin(pin, pin_path)`, so that shape-doctor.py can check the pins in a root's history in memory. The `update-pin-row` raises moved into `check_pin`, and on this branch they call `pin_rows_remediation(root)`, #290's `git -C <root> log` advice, where `root` is not a name. The textual merge is clean and the tree dies at run time: `NameError: name 'root' is not defined` for a `files:` of `5`, `true` or `1.5` in `check`, `apply` and shape-doctor.py, which then prints no report at all. This commit takes the planned #267 composition, the PR's two attachments fix1-compose-267-resolution-v2.patch and root-pin-faults-after-267.patch, applied unchanged; main moved their hunks by offset only (shape-doctor.py 526 lines, tests/test_shape_doctor.py 521, update-shape.py 1): - update-shape.py: `check_pin(pin, pin_path, root)`, `read_pin` passes its root, and the docstring says `root` is the repository whose history a refusal's `git log` reads. - shape-doctor.py `pin_reads_at` passes `ctx.root`. Its docstring says `check_pin` refuses `files: 5` as `update-pin-row`, and `TypeError` leaves its `except` tuple: no pin's bytes raise one now, so one means a caller out of step with `check_pin`, and the doctor fails loudly rather than answer that no commit holds a pin to put back. - tests/test_shape_doctor.py: #267's ROOT_PIN_FAULTS gains `files: 5`, `true` and `1.5`, each `update-pin-row`, so each is INVALID, exit 1, with `checkout HEAD` as its NEXT, which run as printed leaves the root COMPLIANT. Main's other changes to this PR's files merge as text away from these lines: #446 (245d775) in validate-pins.py, #406 (ec8e09a) in validate-family.py, #379 (87d2eb7) in update-shape.py, and #375 (cd1fc79) and #357 (5678f68) in shape-doctor.py. Neither module has changed on main since decff87. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…every pinned copy with HEAD before its first write and refuses a difference as `update-branch-uncommitted`, so an in-place `apply` left uncommitted no longer ends in a commit its own validate-pins.py refuses, in git's "nothing to commit", or in "nothing to do" with no branch made (#436) (#474) Every verdict `apply` reaches is read from the working tree (`read_pin`, `classify`, `_nothing_to_do`), and the commit `--branch` makes is HEAD plus only the paths the run writes. #379 made the `--pr` re-run compare the two; every other `--branch` run still mixed them. Measured on d6df36b with local bare remotes and a logging `gh` stub, upstream B, C and D each changing one copy, after an in-place `apply --at C` left uncommitted on main: `apply --at D --branch shape/d` exited 0 and committed Makefile, the pin and the manifest, whose own validate-pins.py exits 1 on two copies the commit left out; `apply --at C --branch shape/c [--push --pr]` said "nothing to do", exit 0, and made no branch. On a branch whose HEAD re-pins B, the same in-place C then `apply --at B --branch <it>` wrote, rolled back and exited 2 on git's "nothing to commit". An uncommitted edit taken with `--accept-local`, and an uncommitted manifest edit, rode the same gap: the first commit was red, the second carried the edit under the re-pin's message. - Before the first write, and before "nothing to do" is answered, every `--branch` run asks `_refuse_what_head_does_not_commit`: the pin, the kind's manifest and every pinned copy, compared with HEAD by bytes (#379's `_not_what_head_commits`: `git hash-object` against `HEAD:<path>`, the clean filter run, no index flag consulted). - A difference on the re-run `_awaits_its_pull_request` recognises is still `update-rerun-uncommitted`, word for word. On any other `--branch` run it is the new `update-branch-uncommitted`, naming HEAD, each path, and both pins where they differ; its remediation is to commit those changes (on a branch, never the tracking branch) or put them back to HEAD's bytes, then run the same command again. Nothing is written, no branch is made, `gh` is not asked and nothing is pushed. Asked first, it also comes before `update-local-drift` and `update-conflict` for an uncommitted edit; once the edit is committed, those refusals follow as before. - The commit's pathspec is not widened: what a person left uncommitted is theirs to commit or put back. Paths stay explicit, nothing is taken from the index, and README and untracked files are still not consulted. - `_not_what_head_commits` now treats a path neither the disk nor HEAD holds (a pinned copy deleted and committed) as no difference, so that copy is still `update-conflict`'s `copy-missing`; and where a symlink stands on the path's way it compares the link's own text with HEAD's entry for the link, which is what git commits, so a pinned copy kept as a committed link is not refused as a change HEAD lacks. A pinned copy deleted and not committed is now refused as `update-branch-uncommitted` rather than `update-conflict`. - A directory where HEAD commits a directory (a tree, or a gitlink, which git checks out as one) is no difference either: a pinned copy replaced by a directory, that type change committed, has nothing for `git hash-object` to read, and was refused `update-branch-uncommitted` for good by a remedy that cannot change it. It reaches `classify`'s `copy-missing` and `update-conflict`, as on main, before anything is written. Any other pair of kinds is still `update-branch-uncommitted`: a directory over a committed file (staged or not), a file over a committed directory, a directory where HEAD commits nothing. - What stands at a path is `lstat`ed before git is asked to read it. A FIFO, a socket or a device is never read and is no difference: git commits none of them, and `classify` already calls such a copy `copy-missing`, so `update-conflict` names it, with main's exit code and output. Handed to `git hash-object`, a FIFO blocked the open for good and every `--branch` run hung where main refused; a socket was refused as `update-branch-uncommitted`, which no remedy changes. - Under `--branch`, after `update-conflict` and `update-local-drift`, the pin, the manifest and every pinned copy the run writes or re-pins are walked for a symlink at or above them (`symlink_on_the_way`), and each one found is refused as `update-rewrite-symlink` before the first write. The comparison holds a committed link to be HEAD's, but the run reads, writes and re-pins the bytes at its far end, so a copy upstream rewrote through a link, an edit at a link's end taken with `--accept-local`, and the pin or the manifest kept as a link each exited 0, on main too, over a commit holding the links as HEAD had them, whose own validate-pins.py is red in a clean clone. The remedy is to replace the link with what it points at, commit that on a branch, and run the same command again. A linked copy the run leaves unchanged is not refused: the pin keeps HEAD's row for it. The name is the one #286's fix gives the same refusal in every mode. - Without `--branch` (the issue's a'), the run writes as before. Its NEXT line, which pasted made the same red commit, now names the pinned copies it did not write whose bytes HEAD does not commit, says the pin records them, and prints no commit to paste: commit them with the run's paths, or put both back and run apply again. A branchless run is not refused: the suite runs it over uncommitted edits on purpose (`--accept-local` over a local edit, a hand-edited pin), and nothing is committed there. Unchanged: a clean tree, byte for byte where diffed (the probe's clean-tree `--branch`, in-place and current-branch runs, and the README-and-untracked run, give identical output on d6df36b and on this change; the clean `--branch` run again on 03dc729 and on the final head); #379's re-run refusal and its tests; #202's rollback; `check`; every `--help` text; AGENTS.md, which does not name the new code. #285's two refusals keep their places: `update-add-not-a-directory` is asked first, in `resolve_additions`, and a write that fails after this comparison is still `update-write-failed`, rolled back to the bytes it found to be HEAD's, so the same command run again lands (probed on d6df36b and on this change, identical output). Left out: the branchless run that moves the pin back to what HEAD already commits still prints a commit line git answers with "nothing to commit", which no commit can make red. A branchless run still writes through a symlink, as on main (#286). An accepted edit at a link's end that is already committed is refused under `--branch` too, where main's commit was green; the remedy lands it. A manifest replaced by a FIFO hangs `apply` in every mode on main, in `_repin` (reading the manifest), after the copies and the pin are written; under `--branch` the run now reaches that point as main does. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #333 (comment)
What
_awaits_its_pull_requestrecognises anapply --branch --prre-run, and beforeghis asked or anything is pushed,_refuse_what_head_does_not_commitcompares the pin, the kind's manifest and every pinned copy with what HEAD commits, by bytes:git hash-object <file>againstHEAD:<path>. The clean filter runs, and no index flag hides an edit.REFUSED update-rerun-uncommitted. The refusal names those paths, HEAD, and the commit the pull request would be titled with. Where the working tree's pin and HEAD's name different commits, it names both and says the pull request "would name one commit and carry another". The remediation: commit those changes on the branch or put them back to HEAD's bytes, then run the same command again.Why
Measured on main ab623dd and on this head with local bare remotes and a
ghPATH shim that logs its argv and answerspr listwith[]. A = the fork point, B changesscripts/validate-manifest.py, C changesscripts/validate-pins.py.shape/update-landing, then an in-placeapply --at Cleft uncommitted, thenapply --at C --branch shape/update-landing --push --prpushed. Rangh pr list, thengh pr create --title "Re-sync the shape copies to opensoft/openRepoShape @ C". The remote branch's pin is BREFUSED update-rerun-uncommitted: … does not hold what HEAD commits at contracts/shape-pin.yaml, project.yaml, scripts/validate-pins.py … there the pin names C and HEAD's names B, so the pull request would name one commit and carry another.ghnot asked, nothing pushed, tree untouchedorigin/main's by hand, then--at A@ Awhile the branch carries Bproject.yamledited, not committedThe issue was right, and is now measured rather than read from the code. Its direction was "a clean pin and manifest". This PR also requires every pinned copy to be clean: otherwise a hand commit of the pin without its copy still pushes a branch whose own validator is red.
Tests
tests/test_update_shape_landing.py::test_a_rerun_over_a_tree_head_does_not_commit_is_refused_by_name, with the cases in-place-apply, put-back-by-hand, manifest-edit and copy-uncommitted. All four fail on main ab623dd (exit 0, "nothing to write") and pass here.test_dirt_neither_decision_reads_leaves_the_rerun_as_it_waspasses on both. It pins the kept behaviour and guards against a refusal that reaches too wide.ghstub, started through itssitecustomizehook (tests/test_update_shape_landing.py: 8 of its 19 tests driveupdate-shape.pywith a POSIX shellghstub and are skipped on Windows, so the--prlanding and re-run paths (#204) are run by two of the three CI jobs #334). A tool that askedghwould be heard, and they pass under the sessionghguard (tests/conftest.py: a sessionghguard first on PATH answers the doctor's twomachine-row probes as aghwith no login and fails any test whose other calls it refused, so no test reaches the operator'sghand the row is still tested, andrefusing_network's four users assert on the calls itsghlogged rather than on a stderr the preflight captures (#296) #349). No test here carriesWINDOWS_SKIPor any other skip, so all five run on ubuntu, macOS and Windows.scripts/validate-pins.pyand nothing else. No existing test reads the upstream's tip.test_update_shape_landing,test_update_shape,_add,_add_ignored,_add_symlink,_pr_base,test_shape_doctor,test_commit_trailers,test_validator_child_encoding,test_load_yaml_refusals,test_windows_paths,test_repo_hygiene,test_cli_reference,test_git_output_decodingandtest_stdout_encoding: 879 passed, 7 skipped. The seven skips are other files' OS-only tests: three junction tests and three PowerShell tests that run only on Windows, and one about Windows' own stdout default.Composes with
git merge-tree --write-tree --name-only <branch> 2575d41:rewrite_shape_pinand bump-leg.pyrewrite_pinfindtree_sha256:by exactly two leading spaces and replace whole lines, so acontracts/<role>-pin.yamlwhosedigests:child is indented four is refused as hand-edited, and a trailing comment on acommit:ortree_sha256:line is dropped by every pin rewrite,rewrite_manifestincluded #323), local 7e2230c and origin dc0fa7b: textually CLEAN, but NOT semantically. That branch removesYamlErrorandparse_yamlfrom update-shape.py'srepo_shapeimport, because its last user there moves toshape_materialize, and this PR's_committed_pinuses both names. On a scratch merge of this head with 7e2230c and dd14195,test_update_shape_landing.py,test_update_shape_next_quoting.pyandtest_pin_rewrite_layout.pygive 4 failed, 203 passed: the four refused cases raiseNameError. With the two names put back in that import,test_update_shape_landing.pyandtest_pin_rewrite_layout.pygive 153 passed. Whichever of the two lands second must keep both names imported.check's andapply's NEXT lines print the root path unquoted wherelanding_failed's and the rollback hand-fix lines quote it withshlex, so a root under a path with a space pastes as a different command, and none of them quotes for cmd.exe or PowerShell #325), local dd14195: CLEAN. It is in the scratch merge above and its tests pass there.contracts/shape-pin.yamlor the manifest kept as a symlink, or under one, is refused asupdate-rewrite-symlinkbefore anything is written, andcheckcalls such a copycopy-is-link, soapplyno longer writes the standard's bytes at the link's end and reports a commit that does not hold them (#286) #347 fix/update-shape-pinned-copy-symlink e965ab0: CLEAN. Itscopy-is-linkrows and itsread_pinandpreparerefusals stop a linked copy, pin or manifest before this check is reached (read from its code, not run).apply --addrefuses a path under a file the root keeps asupdate-add-not-a-directorybefore it writes anything, and a write that fails after the copies isupdate-write-failed, rolled back, so neither leaves a rewritten copy and an unmoved pin behind a traceback (#285) #346 fix/update-shape-add-not-a-directory 818db36: CLEANread_pinrefusesupdate-pin-row, and the pinned validate-pins.py and validate-family.py reportshape-pin-row, when a shape pin'sfiles:is not a list, sofiles: 5,trueor1.5no longer endscheck,apply, shape-doctor.py and the project's own validator inTypeError: ... is not iterable(#290) #343 fix/pin-files-not-a-list 039530b: CLEANcontracts/shape-pin.yamlthat does not read as a pin (a merge conflict, a non-UTF-8 byte, acommit:that is not 40 hex, a deleted file) isINVALID, exit 1, with the git command that puts back the last pin a tool wrote as its NEXT, instead ofCANNOT ANSWER, exit 3, sending the reader togit fetch --allin the standard (#237) #267 fix/doctor-root-shape-pin-unreadable 8badae0: CLEANmanifest kindsrow puts back a shape or leg pin undercontracts/that does not load with the git commandpin_restorepicks from its own state, shows a copy the shape pin has a row for or a neutral product pin with a read-onlygit diff, and leaves any other file to a person whatever it is called, so its NEXT no longer tells the reader to delete the pin (#291) #358 fix/doctor-manifest-kinds-unreadable-pin ebffb08: conflicts in shape-doctor.py, which this PR does not touch. It has the same conflict with origin/main itself, so its stale base causes it, not this change.Left out
apply --at D --branch <new>over an uncommittedapply --at Cexits 0 with a commit whose ownvalidate-pins.pyexits 1 (measured on ab623dd).--prrun whose working tree needs a write that HEAD already holds (the tree at C over a branch at B,--at B) still ends in git's "nothing to commit", exit 2 and rolled back, rather than in a named refusal (measured on ab623dd and here).apply --at C --branch <new> --prover an uncommitted in-place C on the tracking branch still says "nothing to do" and exits 0, with nothing committed, pushed or proposed (measured on ab623dd and here).contracts/shape-pin.yamlor the manifest kept as a symlink, or under one, is refused asupdate-rewrite-symlinkbefore anything is written, andcheckcalls such a copycopy-is-link, soapplyno longer writes the standard's bytes at the link's end and reports a commit that does not hold them (#286) #347 lands, a pin, a manifest or a pinned copy kept as a symlink is always named by this refusal, even when everything at the link's end is committed, becausegit hash-objecthashes the bytes the link leads to while HEAD's blob at that path is the link itself (read from git's behaviour, not run through the tool).Closes #333
🤖 Generated with Claude Code
Summary by Sourcery
Refuse pull-request re-runs when the working tree does not match the branch commit they would finish, preventing a pull request from naming one pin while carrying another.
Bug Fixes:
Enhancements:
Tests: