Repository navigation
adopt-project.py: check finds, and execute refuses before any leg exists, a path the root keeps at the shape's own pin and a --work-dir this user cannot write in, so contracts/shape-pin.yaml kept in the root and a chmod 500 work directory no longer pass check and fail only after both legs are pushed (#524) - #581
Conversation
… exists, a path the root keeps at the shape's own pin and a `--work-dir` this user cannot write in, so `contracts/shape-pin.yaml` kept in the root and a `chmod 500` work directory no longer pass `check` and fail only after both legs are pushed (#524) Two v1.0.0 known limits on #457, L1-K1 and L1-K10, measured on main 8beb3d9 and again on bfed143. L1-K1. `_kept_pin_findings` (#273) asked whether the plan keeps a path where `execute` writes each declared pin's file, and never asked it of the shape's own pin, `contracts/shape-pin.yaml`. A file kept there, with a pin or without, and a link there that leads to something (`-> openglass-pin.yaml`, which finds the pin `place` writes first, or `-> README.md`) passed `check`; `execute` made and pushed both legs, wrote the shape's pin beside it under `shape/`, and exited 1 with `adopt-shape-pin-unreadable`. The same rule now asks it of the shape's pin (`_pin_files`), under #273's code, `plan-root-holds-pin`, with a sentence that names the shape's pin and what reads it. A link there that leads nowhere was #504's walk's finding (`plan-root-symlink`); the kept-pin rule names it now, as it names a link at a declared pin's file, and the walk does not say it again (`_links_at_a_pin`): one link, one finding. L1-K10. `--work-dir` was made and never written in before `_create_leg_remotes`. With `chmod 500` on it, `check --work-dir` said `plan ok`, and `execute --work-dir` made and pushed both legs and stopped `extracting the spec leg`, exit 2. `execute` now makes and removes a directory in it first, before it opens the source, and refuses as `adopt-work-dir-unwritable`, naming it. `check --work-dir` names the same as a finding beside the plan's own, and refuses it as `execute` does where an `org/repo` source would be cloned there, which ended in a PermissionError traceback. Three tests used the source's own `contracts/shape-pin.yaml` kept in the root to make a verification fail after both legs; that plan is refused before any leg now. `test_adopt_late_failure.py` and `test_adopt_github_exits.py` fail it with a `pre-commit` hook that takes a pinned copy out of the split commit (`conftest.a_hook_that_unstages`), and `test_adopt_ignored_shape_files.py` runs `execute` in process with the shape's pin left out of the rule, as before #524, so the verification stays tested as the backstop. tests/test_adopt_shape_pin_and_work_dir.py adds 17 tests, each failing on bfed143. Closes #524 Lane: openreposhape-1 (openRepoShape-1) 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 23 hours by commenting |
Reviewer's GuideThe PR closes two adoption preflight gaps: it detects root content at the shape's own pin and rejects unwritable explicit work directories before source access or leg creation, while updating collision, symlink, and late-failure tests to reflect the earlier refusals. Sequence diagram for adoption preflight refusalssequenceDiagram
participant User
participant Execute as cmd_execute
participant WorkDir as _unwritable_work_dir
participant Source as open_source
participant Remotes as _create_leg_remotes
User->>Execute: execute(plan, --work-dir)
Execute->>WorkDir: _refuse_an_unwritable_work_dir(args, work_root)
WorkDir->>WorkDir: tempfile.mkdtemp(dir=work_root)
WorkDir->>WorkDir: os.rmdir(probe)
alt work directory is unwritable
WorkDir-->>Execute: Refusal(adopt-work-dir-unwritable)
Execute-->>User: No source opened and no leg repository created
else work directory is writable
WorkDir-->>Execute: None
Execute->>Source: open_source(args.source, work_root)
Execute->>Remotes: _create_leg_remotes(...)
Remotes-->>User: Leg repositories created
end
Sequence diagram for check work-directory validationsequenceDiagram
participant User
participant Check as cmd_check
participant Probe as _unwritable_work_dir
participant Source as plan.open_source
User->>Check: check(--work-dir)
Check->>Probe: _unwritable_work_dir(args, work_root)
Probe->>Probe: tempfile.mkdtemp(dir=work_root)
Probe->>Probe: os.rmdir(probe)
alt probe fails
Probe-->>Check: adopt-work-dir-unwritable
Check->>Source: open_source(args.source, work_root)
alt source requires cloning into work directory
Source-->>Check: OSError
Check-->>User: Refusal(adopt-work-dir-unwritable)
else source is readable
Source-->>Check: source
Check-->>User: Finding adopt-work-dir-unwritable
end
else probe succeeds
Probe-->>Check: None
Check->>Source: open_source(args.source, work_root)
Check-->>User: Plan findings
end
Flow diagram for shape-pin collision detectionflowchart TD
A[check or execute loads plan] --> B["_pin_files(plan)"]
B --> C[Declared pin files]
B --> D[contracts/shape-pin.yaml]
C --> E[_kept_pin_findings]
D --> E
E --> F{Root keeps a file, directory, or symlink at pin path?}
F -->|yes| G[plan-root-holds-pin]
F -->|no| H[Continue preflight]
G --> I[check reports finding or execute refuses]
I --> J[No leg repository exists]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
…y-hand test and #581 among them) into #569, so the PR's CI runs on current main Merges origin/main fb786c1, read from the shared store without a fetch. Main moved from 7d9bec9 (#569's last merge) by #556, #557, #558, #559, #560, #562, #567, #568, #579, #580 and #581. #579 (#550) landed tests/test_setup_by_hand_commands.py, which this PR's `--no-next-steps` makes red; the next commit answers it. The merge itself is clean, and its tree is the pm569 prediction's #581 step (53b6d0f7)'s, so the resolution prepared against the predicted main carries over. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…581's conftest import with #582's follow-up import, and fail #582's verification test with #581's hook Two resolutions, one textual and one semantic: - tests/test_adopt_github_exits.py, the import block (the only conflict). Kept #581's whole `from conftest import (ADOPT, REPO, WINDOWS_SKIP, a_hook_that_unstages, git, make_source_repo, run_script, write_plan)`, and #582's `from test_adopt_tracking_branch import OPEN_THE_SPLIT, PULL_REQUEST_FOLLOW_UP` after it. - tests/test_adopt_github_exits.py, `test_a_failed_verification_says_what_it_left_in_place` (#582's). #581 (#524) refuses a kept `contracts/shape-pin.yaml` before any leg exists, so it deleted `FAILING_TREE`, `CONTRACTS_TO_SPEC` and `KEEP_CONTRACTS`, and renamed `github_run`'s `keep_contracts` to `failing`, which fails the verification with `a_hook_that_unstages`. The test now calls `github_run(tmp_path, refuses, failing=True)`. No other use of the removed names is left in tests/. AGENTS.md step 7 and the auA-12 texts name no cause of a failed verification, only what one leaves in place, and #581 changes neither, so neither needed an edit. The auto-merged adopt-project.py and tests/test_adopt_late_failure.py carry exactly main's change (the same `git patch-id` as 2e6c6fa..fb786c1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fix/540-pin-source-unread This merges origin/main 2bf8fd4 as the shared store held it at 2026-10-15T16:50Z, thirteen landings past the 7d9bec9 this branch last merged: #555 (shape-doctor's manifest kinds), #565 (scaffold's `scaffold-remote-exists`), #581 (adopt's `check` of a kept pin path and an unwritable work directory), #568, #559, #557, #560, #580, #556, #558, #567, #579 and #562. It is done so that the PR's CI merge ref and this round's rewording run on current main. The merge was clean, with no conflict hunk. `adopt-project.py`, `scaffold-project.py`, `scripts/shape_materialize.py`, `docs/cli.md` and `tests/test_adopt_root_symlinks.py` took main's changes beside this PR's by the textual merge, and `scripts/render-cli-reference.py --check` passes on the result. Main: 2bf8fd4. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Main moved while this merge was being made: a61cf47 (#589) at the coordinator's request, then 70331aa (#586, shape-doctor.py only) and 2989b4c (#573: family.py, scaffold-project.py, docs/cli.md's scaffold and family sections, their tests). This merges 2989b4c, the tip at merge time. 10 commits since 2e6c6fa: 8890f57, fb786c1 (#581), 26c1600, 2bf8fd4, 654c476 (#597), 3f768a6, 024ea55, a61cf47 (#589), 70331aa (#586), 2989b4c (#573). One conflict, adopt-project.py, the end of `_entry_findings`' return: HEAD (#520) added `names_remediation` to the remediation list, so a misnamed `adopt_branch:` or `tracking_branch:` prints its remedy; main (#589, #449) added `pin_notes` to the notes, so a pin whose digest `check` could not recompute is said once at the end. Resolved by keeping both, each where its side put it: + names_remediation + tracking_remediation + moved_remediation + field_remediation, pin_notes + notes) The two edits touch different elements of the returned tuple, so neither changes what the other means: #589's `digest_findings, pin_notes = _pin_digest_findings(plan)` and its docstring paragraph auto-merged beside #520's `_branch_name_findings` call and paragraph. docs/cli.md auto-merged (#573 changed the scaffold and family sections, this branch the adopt section); `scripts/render-cli-reference.py --check` passes on the merge. Against the new main, each of this branch's six files (adopt-project.py, docs/cli.md, tests/test_adopt_branch_names.py, tests/test_adopt_plan_yaml_quoting.py, tests/test_adopt_split_branch.py, tests/test_adopt_template_values.py) differs by exactly the +/- lines it did against 2e6c6fa at c598ec3 (adopt-project.py: 399). Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eck`'s finding for a link kept above a path the shape writes says the clone holds it as a file, so `scripts -> tools/deep` no longer says where the scripts land and sends the person to change where it points, on to `adopt-symlinks-off` (#531) (#584) Some disks check a symlink out as a plain file: under `core.symlinks=false`, which is Git for Windows' default and what `git init` writes on a filesystem that cannot hold a symlink. On such a disk, a symlink the plan keeps in the root ABOVE a path the shape writes is a plain file in the assembly's clone, and nothing can be written beneath it. When `check` reports such a link as `plan-root-symlink`, its finding now says that, in `adopt-symlinks-off`'s words, and gives a fix that works on that disk: send the link to a leg or `drop` it in the plan, and run `check` again. `execute` refuses with the same line, word for word, before any leg exists, as it refused the old line. Only the text of a finding `check` already made changes. A link above that the walk follows cleanly (`scripts -> ci`) stays `execute`'s `adopt-symlinks-off` before any leg, as #504 designed. With symlinks on, the output is byte-identical to `main`'s. `_a_file_above(walk, probe)` runs only for a walk that is about to be reported, so a plan with no such finding makes no new probe, and no network or gh call is added. Tests: tests/test_adopt_root_symlinks.py holds 149 tests on `main` and 156 here. `test_with_symlinks_off_a_link_above_a_path_is_named_as_a_file` (6 cases) and `test_with_symlinks_off_the_fix_the_finding_gives_works_there` (`code`, `drop`) are new. Three existing tests of a followed link gain `links_as_links`, so they keep asserting the symlinks-on line wherever the runner's checkout writes a plain file, which a Windows runner's may (Git for Windows' default). Main's test of the old reason is now the D6 row. Review: the adversarial review found the change PR-READY at 0a178e4, with five text corrections, all in the PR body. The edge cases it found outside #531 are left for #457's checklist. `main` was merged in five times, and the branch was never rebased. - **The first merge** was GitHub's update-branch, at 348f8a8 (main 2e6c6fa). 348f8a8 is green on Linux, macOS, Windows and SonarCloud. - **The second merge was by hand,** at cae5a98 (main 654c476), after #581 (#524) changed `adopt-project.py`. Its one conflict was a comment in `_SymlinkProbe`, about a work directory no repository can be made in. The resolution keeps both #531's sentence and #524's. - **cae5a98c** passed 71 adopt test files in both modes: 5,796 passed and 5,482 passed, with 0 failed. - **The third merge** was GitHub's update-branch, at 7f439a5 (main a61cf47), after #589 changed `adopt-project.py`. It was a clean merge. - **The fourth and fifth merges** were also GitHub's update-branch, both clean, after #566 and #582 changed `adopt-project.py`: 250b070 (main 1d809ea) and c43cde1 (main 526cd2c). - **Since #597, CI is Linux only.** The head is green on Linux and SonarCloud (ruling 27). Closes #531 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-1 (openRepoShape-1)
Claim: #524 (comment)
What
Two plans passed
adopt-project.py check, andexecutefailed on them only after it had made and pushed both leg repositories.checknow finds both, andexecuterefuses both before any leg repository exists. They are lines L1-K1 and L1-K10 of #457, filed as #524.contracts/shape-pin.yaml, with a pin declared or without. Each is nowplan-root-holds-pin, adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273's finding. Until now that finding asked only about each declared pin's file. As adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 does for a declared pin's file, it also asks once case is ignored, so a case twin such ascontracts/Shape-Pin.yamlis found too. On Linux such a twin adopted and verified onbfed143; it is refused now on every system, as a declared pin's twin is. One link gives one finding from these two rules (an empty-target link is adopt-project.pycheckpasses a source whose tree holds a symlink with an empty target, so with symlinks onexecutecreates both leg repositories and then fails cloning the source (on main with exit 2 and nothing on stderr, #171), and withcore.symlinks=falseit saysadoption verifiedover a leg or root that no clone with symlinks on can check out #303's as well; see Known limits). A link there that leads nowhere used to be adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504'splan-root-symlink. It is now the kept-pin rule's, and the walk does not report it again.--work-dirthis user cannot write in.executefirst makes a directory in the work directory and removes it again. It does this before it opens the source. If that fails, it refuses asadopt-work-dir-unwritableand names the directory.check --work-dirreports the same thing as a finding, next to the plan's own findings. Where anorg/reposource would be cloned into that directory,checkrefuses it asexecutedoes.Why (repro on main)
Measured on main
bfed143(the v1.0.0 release commit). Each run used a local source,--local-remote-dir, and a stubghfirst on PATH that nothing called.checkexecutecontracts/kept,contracts/shape-pin.yaml -> openglass-pin.yaml,--pin openGlass@<commit>(the issue's)plan okFINDING adopt-shape-pin-unreadablecontracts/kept, a filecontracts/shape-pin.yaml,--pinplan okadopt-shape-pin-unreadableplan okadopt-shape-pin-unreadablecontracts/shape-pin.yaml -> README.md(a kept file), no pinplan okadopt-shape-pin-unreadable--work-dirwithchmod 500plan okREFUSED extracting the spec leg..github -> cikeptplan okREFUSED extracting the spec leg.plan okadoption verifiedOn this branch (
17dd55ca), every case above except the control givescheckexit 1 with exactly one finding:plan-root-holds-pinfor the first four rows, andadopt-work-dir-unwritablefor the last two.executeexits 2 with the same line, and no leg repository exists. The control still adopts and verifies.How
_pin_files(plan)lists each declared pin's file, then the shape's own pin, in the orderplacewrites them._kept_pin_findingsand_links_at_a_pinboth read this one list. So the kept-pin rule and the walk's exclusion cannot disagree about which files are pins.--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273's code,plan-root-holds-pin, its opening and its fix ("sendcontracts/to a leg,dropit, or rename it in the source and re-runplan"). This is the same rule, extended to one more pin file. Only the middle of the sentence differs, because the two pins are read by different things. It names the shape's own pin (the openRepoShape commit the root's copies came from, and each copy's digest). It names what reads that pin there:execute's verification, the root's ownvalidate-pins.pyandmake bootstrap. And it says what the split would hold: the source's file, directory or symlink at that path, with the shape's pin beside it undershape/. For a symlink it adds "or written through it where it leads nowhere"._unwritable_work_dir(args, work_root)runs only when--work-diris given. It callstempfile.mkdtemp(dir=work_root)and thenos.rmdir. On anOSErrorit returns a sentence that names the directory and the operating system's reason. Without--work-dir,_work_rootmakes a new directory that this user owns, and nothing is checked.cmd_executeruns_refuse_an_unwritable_work_dirright after_work_root, beforeopen_source. So it comes after onlyPlan.loadand_refuse_a_mistyped_plan, which read the plan, and beforeopen_source,_require_filter_repo, every other refusal and_create_leg_remotes.cmd_checkruns the same probe at the same point. It lists the result first among its findings. If the probe failed, it turns theOSErrorfrom making anorg/reposource's clone directory there into a refusal with the same code and words.adopt-work-dir-unwritablesits next toadopt-work-dir-occupied. It has the same remediation shape: "no leg repository was created and nothing was pushed. Pass a --work-dir this user can write in, or none: without one, each run makes a new one."Tests
New:
tests/test_adopt_shape_pin_and_work_dir.py, 17 tests. Every one fails withadopt-project.pyas onbfed143(the revert table's last row: 17 of 17, withgit filter-repo). The first 16 were written before the change and run onbfed143itself: 16 failed withgit filter-repo, and 15 failed and 1 skipped without it. The file imports its helpers and fixtures fromtests/test_adopt_root_symlinks.py, including J4'sunwritable_work, instead of copying them.contracts/Shape-Pin.yaml) and a directory of that name, each with a pin declared and without (6 tests).checkprints exactly that one finding, andexecuterefuses with that line alone, before any leg exists.placewrites first (the issue's case), to a kept file, leading nowhere with a pin and without, and a case twin leading nowhere. Each gives exactly one finding,plan-root-holds-pin, and noplan-root-symlink.git filter-repo):executeexits 2 withREFUSED plan-root-holds-pin, and no leg remote exists.check --work-dirwith no link and with.github -> ci. A writable directory passes and is left empty. An unwritable one gives the finding, word for word.check --source acme/thingwith an unwritable--work-dirrefuses withadopt-work-dir-unwritable, where it ended in a PermissionError traceback. Nothing asksgh.executewith no link and with.github -> ci, in process and on the command line. Each refuses with the same words before any leg exists.Changed: three tests that used the source's own
contracts/shape-pin.yamlkept in the root to make a verification fail after both legs. That plan is now refused before any leg exists, so each test needed another way to fail the verification:tests/test_adopt_late_failure.py::test_a_failed_verification_is_flushed_and_its_way_out_runsnow fails it with apre-commithook that takes a pinned copy (scripts/validate-pins.py) out of the split commit. The hook comes fromconftest.a_hook_that_unstages, through a globalcore.hooksPath, as the file'spre-receivehooks already come. The test's way-out half is unchanged: it re-runs without the hook.tests/test_adopt_github_exits.pydoes the same throughgithub_run(failing=True), fortest_a_failed_verification_outranks_a_refused_pull_request. Its control,test_the_same_source_verifies_when_gh_opens_the_pull_request, runs without the hook. Both stayWINDOWS_SKIP, as before.tests/test_adopt_ignored_shape_files.py::test_a_source_pin_kept_in_the_root_fails_verification_with_the_plan_answerkeeps its source and all its assertions about adopt-project.py_commit_the_splitstages withgit add -A -- ., so a source.gitignoreof.*silently drops.gitattributesandvalidate.ymlfrom the split,executestill saysadoption verified, and the assembly fails its first bootstrap #167's finding, but changes how it runs. It first asserts thatchecknow exits 1 withplan-root-holds-pin. It then runsexecutein process with the shape's pin left out of_pin_files, as before adopt-project.pycheck: refuse a kept file at the shape's pin path, and an unwritable--work-dir, before any leg exists #524, and the verification still reports the problem. This follows the file's own_stage_the_splittest of adopt-project.py_commit_the_splitstages withgit add -A -- ., so a source.gitignoreof.*silently drops.gitattributesandvalidate.ymlfrom the split,executestill saysadoption verified, and the assembly fails its first bootstrap #167.tests/test_adopt_root_symlinks.py: one docstring now says thatplan-root-holds-pinanswers the issue's link, where it said that the verification fails.Runs at
17dd55ca. The head was clean (dirty 0) and every log endsEXIT=0. All runs used/usr/bin/python3 -m pytestwith a stubghfirst on PATH, a bogusGH_TOKENand an emptyGH_CONFIG_DIR. The stub logged no call. The file set has 70 files and 5,720 tests: every file that namesadopt-project,adopt_projector conftest'sADOPT, everytests/test_adopt_*.py,tests/test_shape_doctor.pyandtests/test_scaffold_leg_path_collisions.py. It ran in five chunks per mode.git filter-repoon PATHPATH=/usr/local/bin:/usr/bin:/bin, stub first)In hidden mode, the only test of the new file that skipped is the one command-line
executerun that needsgit filter-repo.Revert table. Each change was reverted alone in
adopt-project.py, in a scratch checkout of17dd55ca. Each row lists the tests that then fail, out of the 17 new tests and the 5 touched ones. Nothing fails with no change reverted (22 passed)._kept_pin_findingsasks about the shape's pintest_adopt_ignored_shape_filestest_links_at_a_pinnames the shape's pinleading-nowhere,leading-nowhere-with-a-pin,a-case-twin-leading-nowhere(two findings each)check'sadopt-work-dir-unwritablefindingcheckagreeing withexecutetest_check_names_a_work_directory_it_cannot_write_in[no-link, a-link-above]execute's refusaltest_execute_refuses_a_work_directory_it_cannot_write_in_before_any_leg[no-link, a-link-above](both legs made)check'sOSErrorfrom the clone becomes the refusalchecktest_check_refuses_a_source_it_would_clone_into_that_directoryadopt-project.pyas onbfed143test_adopt_ignored_shape_filestestThe other four touched tests pass with any change reverted, by design. Three of them only change how a verification is made to fail after both legs, which does not depend on #524. The fourth is a docstring.
Windows
chmodcannot keep a Windows user, or root anywhere, from making a directory. So the five work-directory tests skip there throughunwritable_work, as materialize_assembly_root writes the shape files through a root-kept symlink to anywhere on disk, and crashes with a traceback on a link loop, after both legs are pushed #183's J4 test does.update-index --cacheinfo), as adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504's tests make them. The one command-line test needsgit filter-repo, whichtests-windowsinstalls.tempfile.mkdtemp, which works there.Path.write_textand text-mode stdin write\r\n. With it, the 17 new tests and the 5 touched ones ran at17dd55ca: 20 passed and 2 failed.github_runwrites itsghshim withwrite_text, so under the emulation the shim's#!line ends in\randghcannot start (FileNotFoundError: 'gh').bfed143under the emulation: 3 of 3 GitHub-mode tests run fail, one of them a test this branch does not touch.WINDOWS_SKIP, so a real Windows run never runs them. No test asserts"...\n"in bytes read back from a file written withwrite_text.pre-commithook is not tested on Windows from here. It is LF and#!/bin/sh, like thepre-receivehook thattests-windowsalready runs from the same file through Git for Windows'sh. I did not run it on a Windows host.Not in this PR
Measured on
17dd55ca. Each is a new edge case for #457's checklist, and none is fixed here:contracts -> governancewithgovernance/shape-pin.yamlkept.checkgives 0;executegives 1 withadopt-shape-pin-unreadableafter both legs. For a declared pin (governance/openglass-pin.yaml,--pin openGlass@<commit>),checkgives 0 andexecutegives 0 withadoption verified. But the root'scontracts/openglass-pin.yamlthen leads to the source's file, which the root's ownvalidate-pins.pyreads. Neither the kept-pin rule (it follows no link) nor the walk (the path is taken, and the copy goes undershape/) asks this.--work-dirthat cannot be made, or that names a file._work_root'smkdirraises PermissionError or FileExistsError, as a traceback, inplan,checkandexecute. No leg exists by then.plan --source <org/repo>with an unwritable--work-dir. This is a PermissionError traceback atsource-readonly.checkandexecutenow refuse the same case.docs/release-notes/v1.0.0.mdkeeps its two known-limit lines for adopt-project.pycheck: refuse a kept file at the shape's pin path, and an unwritable--work-dir, before any leg exists #524, because they describe 1.0.Known limits
checkpasses a source whose tree holds a symlink with an empty target, so with symlinks onexecutecreates both leg repositories and then fails cloning the source (on main with exit 2 and nothing on stderr, #171), and withcore.symlinks=falseit saysadoption verifiedover a leg or root that no clone with symlinks on can check out #303'splan-empty-symlinkandplan-root-holds-pin. adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 does the same for an empty link at a declared pin's file, and adopt-project.pycheckpasses a plan that keeps the source's ownshape/in the root, andplace()then writes the shape's collision copy onto it after both legs are pushed: a realshape/README.mdis overwritten whileexecuteprints "nothing was overwritten" and endsadopt-lost, and ashape/Makefile/directory or a file namedshapeis a traceback #262 for one at a copy undershape/(both measured on17dd55ca). Only the walk defers to adopt-project.pycheckpasses a source whose tree holds a symlink with an empty target, so with symlinks onexecutecreates both leg repositories and then fails cloning the source (on main with exit 2 and nothing on stderr, #171), and withcore.symlinks=falseit saysadoption verifiedover a leg or root that no clone with symlinks on can check out #303.--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273's sentence for a declared pin's file is not changed. It says that the pin would be written beside it, also for a link there that leads nowhere, whichplacewrites through. The new sentence for the shape's pin says both.--work-dir, nothing is checked._work_rootthen makes a new directory withmkdtemp, which this user can write in.--work-dirwith a--sourcethat cannot be opened.checkrefuses for the source (source-unresolvable), andexecuterefuses for the work directory (adopt-work-dir-unwritable). Both exit 2 before any leg exists.checkalready refused. A plan that also keepsshape/contracts/shape-pin.yamlgets adopt-project.pycheckpasses a plan that keeps the source's ownshape/in the root, andplace()then writes the shape's collision copy onto it after both legs are pushed: a realshape/README.mdis overwritten whileexecuteprints "nothing was overwritten" and endsadopt-lost, and ashape/Makefile/directory or a file namedshapeis a traceback #262'splan-root-blocks-shape-copybesideplan-root-holds-pin. A hand-editedpins:entry whose product's pin file iscontracts/shape-pin.yaml(plan-pin-collides) getsplan-root-holds-pinonce for that pin and once for the shape's, as two colliding declared pins do onbfed143._commit_the_splitstages withgit add -A -- ., so a source.gitignoreof.*silently drops.gitattributesandvalidate.ymlfrom the split,executestill saysadoption verified, and the assembly fails its first bootstrap #167's verification is no longer reached by the plan answer its test used, the source's owncontracts/shape-pin.yamlkept in the root, whichchecknow refuses. Of the plans measured here, only the link-above case under Not in this PR still reachesexecute's verification of the shape's pin after both legs. Sotest_adopt_ignored_shape_fileschecks adopt-project.py_commit_the_splitstages withgit add -A -- ., so a source.gitignoreof.*silently drops.gitattributesandvalidate.ymlfrom the split,executestill saysadoption verified, and the assembly fails its first bootstrap #167's finding in process, with the shape's pin left out of the rule.Closes #524
🤖 Generated with Claude Code
Summary by Sourcery
Reject shape-pin collisions and unwritable work directories during validation so adoption fails before any leg repository is created or pushed.
Bug Fixes:
Enhancements:
Tests: