Repository navigation
adopt-project.py: under a one-byte locale such as Latin-1, a clone check makes again for a plan's clone record that neither spelling names is compared with both, so it is kept and the next run reads it rather than cloning again (#1112) - #1153
Conversation
…ads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusing `plan-source-gone` (#1078) Under a locale of one byte to a character, Latin-1 say, with `PYTHONUTF8=0`, `os.fsencode` spells every character of a plan's non-ASCII `source.local_path`, so #1072's `_recorded_spelling` asked the path only as written: the e-acute of `Thing` + U+00E9 as the one byte 0xE9, where the name `plan` read in UTF-8 is 0xC3 0xA9. `check` refused `plan-source-gone`, "names nothing that exists", of a source that is there; recorded as a clone `plan` made, it "no longer exists" and `gh repo clone` was asked. `_recorded_path` now asks the spelling as written first and, where that names nothing, the plan's UTF-8 bytes too. `_recorded_source` and `_keep_the_clone_at_the_record` both ask it, so a clone `check --source` made at the record is kept under Latin-1 as under C. Under C and POSIX the lookup is #1072's, and every refusal's words are unchanged: where neither spelling names anything, the path is the spelling as written. The new tests build a Latin-1 locale with `localedef` from a charmap and a locale source they write, and skip with localedef's words where none can be built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…source_and_locale.py: the clone at the record is asked under the C locale and Latin-1 through one helper, `kept_clones` (#1078) SonarCloud counted the Latin-1 test's body as a 14-line copy of the C test's: both built the record and a clone elsewhere, ran `KEEP_THE_CLONE` in a child and parsed its answer. `kept_clones` does that once, in the file that holds `KEEP_THE_CLONE`, and returns whether the locale spells the record, the clones left and the one made elsewhere. Each test keeps its own premise and its assert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heck` makes again for a plan's clone record that neither spelling names is compared with both spellings, so the clone at the plan's UTF-8 bytes is kept and the next run reads it rather than cloning again (#1112) `_recorded_source` decided whether the clone it makes again is the plan's record by comparing its target with the spelling as written alone. Under Latin-1, a plan written in UTF-8 whose clone was in a work directory outside ASCII was cloned again at the UTF-8 bytes `--work-dir` names, put on the list of clones to remove, removed when `check` ended, and cloned again by every run. The new `_recorded_paths` lists the paths `_recorded_path` asks, in its order, and `_recorded_path` now asks them through it. Where neither names anything, the target's real path is compared with each of them. Where one is there, the comparison is with that one alone, as before: a clone kept beside a spelling as written that is there would be refused as `adopt-work-dir-occupied` by the next run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideImplementationFactor the recorded-path spellings into
TestsAdd Latin-1 regression coverage for retaining recreated clones at either spelling
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…plan's UTF-8 bytes hold its clone no longer whole and `--work-dir` names the free spelling as written, the clone `check` makes again there is kept and the next run reads it, and the comparison moves into `_at_the_record` (#1112) The fix round on #1153 from its review, RV1153: - S1: the comparison of a clone made again with the record moves out of `Plan._recorded_source` into a module-level helper, `_at_the_record`, beside `_recorded_paths`, so the method's cognitive complexity falls from 16 back to 15 (SonarQube python:S3776). - N1: the helper compares the clone's target with every path `_recorded_path` asked: where a spelling is there, that one and each asked before it, which names nothing. Under Latin-1, the UTF-8 bytes holding a file where the clone was and `--work-dir` naming the free byte 0xE9, the clone made at 0xE9 was removed when `check` ended and every run cloned it again; now it is kept and the next run reads it. A spelling asked after the one there is still not the record (M2's case, a file at 0xE9, is unchanged). - A test of that reverse case: `check` run twice clones once and keeps the clone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-22T19:46:27Z) Lane 3's independent review of #1153, delta round (RV1153), head ff33ba1. Verdict: READY. No new MUST or SHOULD.
Lane 3's plan: #1153 is READY. It lands only after #1106, which lands after the v1.2.0 tag on a later word. Then lane 3 brings main into this branch, moves its base to main, and lands it by the stacked-PR merges. Lane: openreposhape-3 (openRepoShape-3) |
#1153 (for #1112) was stacked on PR #1106's branch. #1106 has landed on main as the squash 801a7d6, and its final head bd97901 is 504a1dd with main be21751 merged in by update-branch. This is the first of the estate's three stacked-PR merges: it brings that final head in, so the branch holds everything #1106 held at the end. #1153's own change is not touched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…anch's side of #1106's files #1106 landed on main as the squash 801a7d6. This branch already holds #1106's change, through its final head bd97901, so where a hunk conflicts in adopt-project.py or tests/test_adopt_one_byte_locale.py (two of #1106's three files; the third, tests/test_adopt_unenterable_source_and_locale.py, merged clean) this merge takes this branch's side (-X ours), which is #1106's change plus #1153's. Every other hunk of main comes in as main has it. This is the second of the estate's three stacked-PR merges; it makes the branch a descendant of main again, so the pull request shows only #1153's own change. Co-Authored-By: Claude Sonnet 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 6 days by commenting |



Stacked on #1106: its base is #1106's branch. Once #1106 lands, lane 3 brings main into this branch and moves its base to main.
What changed
A follow-up outside v1.2.0 (Brett Heap, 2026-10-21T10:32:04Z). It lands only after the v1.2.0 tag, on a later merge word.
The bug (#1112). Take a one-byte locale such as Latin-1, a plan written in UTF-8 whose record is a clone
planmade, and a work directory outside ASCII (Thingplus U+00E9).checkclones it again at<--work-dir>/source-readonly/Thing. Those are the UTF-8 bytes (0xC3 0xA9) that--work-dirnames, so the new clone is at the plan's record._recorded_sourcethen decided whether that clone is the record withat_the_record. That compared the clone's target with the spelling as written alone, the one byte 0xE9.checkended. Every run cloned again.planorcheckmakes and removes is claimed and checked by path, so a shared, writable--work-dirlets another local user turn the removal onto a directory of theirs, two runs on one--work-dirremove each other's clone, a skipped removal says nothing, and asource-readonlythat is a file ends in a traceback #803's bug under a one-byte locale. adopt-project.py: under a one-byte locale such as Latin-1,checkreads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusingplan-source-gone(#1078) #1106 already fixes the same thing in_keep_the_clone_at_the_record(its mutant M4).The fix (
adopt-project.py), following the issue's Direction:_recorded_paths(recorded). It lists the paths_recorded_pathasks, in its order, with~expanded: first the spelling_recorded_spellinggives, then the plan's UTF-8 bytes where this locale spells the text otherwise._recorded_pathnow asks its paths through_recorded_paths. Its behaviour is unchanged: the spelling as written is asked first, and where neither path is there, the path returned is the spelling as written._at_the_record(target, path, there, recorded), decides whether a clone_recorded_sourcemakes again is at the record. It compares the target's real path with every path_recorded_pathasked:_recorded_paths;paths[:paths.index(path) + 1])._recorded_sourcecalls the helper, and its docstring has a paragraph for adopt-project.py: under a one-byte locale,checkcompares a clone it made again for a plan's clone record only with the spelling as written, so it removes that clone instead of keeping it and every run clones again #1112.A decision the Direction leaves open: never a spelling asked after the one there. The Direction says to compare with both spellings. Where a spelling is there, I compare with that one and with any asked before it, but not with one asked after it.
_recorded_pathreads first.checkwould then read the broken spelling as written first and be refusedadopt-work-dir-occupied. That refusal calls the good clone "no longer whole" (mutant M2 below, measured).checkreads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusingplan-source-gone(#1078) #1106's head: each run clones again, and nothing is refused._recorded_pathasks it first next time, so a clone made there is the record (RV1153's N1, below).Tests (
tests/test_adopt_one_byte_locale.py). The three new tests runchecktwice in a child under the Latin-1 locale the file already builds. A newghstand-in clones a repository on disk.test_a_clone_made_again_at_the_record_is_kept_under_a_one_byte_locale: the first run says the clone is cloned again and keeps it. The second givesplan ok, with no NOTE.ghis asked once in all, and the clone is still there.test_a_clone_made_again_beside_the_spelling_as_written_is_not_kept: a file sits at the spelling as written (0xE9). Both runs giveplan okwith the "no longer whole" NOTE,ghis asked twice, and nothing is left at the UTF-8 bytes. That is adopt-project.py: under a one-byte locale such as Latin-1,checkreads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusingplan-source-gone(#1078) #1106's head's behaviour.test_a_clone_made_again_at_the_spelling_asked_first_is_kept: the reverse. A file sits at the UTF-8 bytes where the clone was, and--work-dirnames the free spelling as written (0xE9). The first run says the clone is no longer whole, clones it and keeps it. The second givesplan ok, with no NOTE.ghis asked once in all, and the clone is still there.Fix round
RV1153, lane 3's review of
590ab96e, posted on this PR, found no MUST, one SHOULD and two NITs. This round,ff33ba12, takes all three:python:S3776:_recorded_sourcehad a cognitive complexity of 16, over the 15 allowed, from the conditional in its comparison. The comparison moved into_at_the_record. Given both versions of the method, SonarQube's snippet analysis reports S3776 at 16 for the one at590ab96e, and nothing for the one at this head or for the helper. Counted by Sonar's rules, they are 15 and 1._recorded_pathasked, as above, so the reverse case keeps its clone (measured below, and its own test). M2's case, a file at 0xE9, is unchanged, and M2 is still killed.plan written toandcloning <repository> (read only) intoname the caller's--outand--work-dirwith_shown, as the other lines of the report name a path, so a bidi control, a line feed or a tab in either no longer reaches the terminal as it was typed (#1115) #1154 is in the merge list.adopt-work-dir-occupiednow begins with the record's state, "is a cloneplanmade that is no longer whole (it is not a directory)", and its way out says to delete the target and run again. At590ab96ethe same case got the plain occupied wording. Both refuse, and neither asksgh(measured).Measurements
Each
checkran under a Latin-1 locale built withlocaledef, where a child reportsiso8859-1 ISO-8859-1 0. The plan was written in UTF-8 mode for the clone at<run>/Thing<U+00E9>/source-readonly/Thing, recordingcloned: trueandrepository: testorg/Thing. The clone was then moved away. Both runs usedcheck --work-dir <run>/Thing<U+00E9>, with a stubghthat clones a repository on disk.504a1dd6ghcalls 1, clone removed at the endghcalls 2, clone removed again590ab96eghcalls 1, clone keptplan ok, no NOTE,ghcalls still 1, clone keptff33ba12ghcalls 1, clone keptplan ok, no NOTE,ghcalls still 1, clone keptThe reverse case (N1): the same plan, a file at the UTF-8 bytes where the clone was, and
check --work-dir <run>/Thing<0xE9>, the spelling as written, which is free.590ab96eghcalls 1, clone removed at the endghcalls 2, clone removed againff33ba12ghcalls 1, clone keptplan ok, no NOTE,ghcalls still 1, clone keptWith a file at the spelling as written (M2's case), #1106's head,
590ab96eand this head give the same result: each run clones again,ghcalls reach 2, and nothing is left.Out of scope
#1112 has no "Needs a ruling" or "Documented limit" section. Nothing else was changed.
Tests
Each file ran on its own at
ff33ba12, under a stubghthat exits 97,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/nullandEMAILset, with/usr/bin/python3 -m pytest -q -p no:cacheprovider(Python 3.12.3):tests/test_adopt_one_byte_locale.py(-rs)tests/test_adopt_unenterable_source_and_locale.pytests/test_adopt_c_locale_bytes.pytests/test_adopt_source_clone_record.pytests/test_scaffold_leg_path_collisions.pytests/test_repo_hygiene.pytests/test_shape_doctor.py590ab96e)tests/test_path_guard_hygiene.py, at the merge of this head into origin/main038dafbatests/test_adopt_one_byte_locale.py(-rs), at that mergetests/test_adopt_unenterable_source_and_locale.py, at that mergetests/test_path_guard_hygiene.py(tests/test_path_guard_hygiene.py and shape-doctor.py: a bare is_file/is_dir/exists guard in a shipped tool fails by file and line unless its_is_aor a reasoned allowlist covers it, and aproject.yamllinked into an unsearchable folder isyaml-unreadable(#430) #1111) is on main and not on adopt-project.py: under a one-byte locale such as Latin-1,checkreads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusingplan-source-gone(#1078) #1106's branch. It ran on a local merge commit of origin/main and this head, built withgit merge-tree --write-treeandcommit-treeand checked out detached. This change adds nois_file,is_dir,exists,is_symlinkoros.pathguard, onlyos.path.realpathandos.path.expanduser.Mutants
Each mutant was applied to
adopt-project.py, andtests/test_adopt_one_byte_locale.pywas run. The file was then restored, andcmpfound it identical._at_the_recordcompares with the path_recorded_pathgave alone (asked = [path]), as on #1106's headtest_a_clone_made_again_at_the_record_is_kept_under_a_one_byte_localeandtest_a_clone_made_again_at_the_spelling_asked_first_is_kept(each second run said "cloned again")asked = paths)test_a_clone_made_again_beside_the_spelling_as_written_is_not_kept(the second run gave exit 2,REFUSED adopt-work-dir-occupied)_recorded_pathreturns the last path, not the spelling as written, where nothing is theretest_the_record_is_asked_as_written_then_as_utf8[neither-latin1]asked = [path] if there else paths), as at590ab96etest_a_clone_made_again_at_the_spelling_asked_first_is_kept(the second run said "cloned again")#1106's M4 (
_keep_the_clone_at_the_record) is unchanged by this PR.Merges
git merge-tree --write-treeat this head,ff33ba12:038dafba: clean.adopt-project.pyortests/test_adopt_one_byte_locale.py: each is clean. They are adopt-project.py:plan written toandcloning <repository> (read only) intoname the caller's--outand--work-dirwith_shown, as the other lines of the report name a path, so a bidi control, a line feed or a tab in either no longer reaches the terminal as it was typed (#1115) #1154, adopt-project.py: a--work-dirlink into a loop that Python 3.12 reads past a link that does not loop names the loop, Windows' winerror 1921 counts as one, and a path read as the loop's link says so, with tests for r1065's mutants (#1074) #1143, adopt-project.py: a recorded source whose.gitthis user cannot read is refusedsource-unreadable, naming that.git, socheckandexecuteno longer call a cloneplanmade with its.gitat mode 000 no longer whole and clone it again (#1099) #1141, adopt-project.py:checkandexecuteask a plan'sorg:andid:right after its three repository names, so a bad one beside an unanswered entry, a moved source, a refused name or another repository is that one refusal in both, and apins:entry that is no pin is said as_read_assays it (#1020) #1125, README.md, adopt-project.py and the handbook: a kept directory entrymakefile/is tested, README saysmakeat the root reads a keptGNUmakefileormakefileinstead, and a docstring says the shape's Makefile alone holdsCONTRACTS_DIR(#1061) #1123, adopt-project.py: anorg/repo--sourcetoo long for the disk is refused as a path is, naming no GitHub repository and offering no url, so--source <300 a's>/Thingis no longer offeredhttps://github.com/<300 a's>/Thing, which can never clone (#1003) #1122, adopt-project.py:execute's NEXT printspython3 scripts/bootstrap.pywhere GNU make at the root reads a keptGNUmakefileormakefile, and its NOTE names that file and what it takes formake bootstrapto run there (#1047) #1121, adopt-project.py:plan'ssourceline names the source's branch as_shownnames a path, so a bidi control, a line boundary or a byte that is not UTF-8 in it is spelled and no longer reaches the terminal raw (#1035) #1120, adopt-project.py: check asks the split's own clone of the source in the work directory, so a clone this git will not make is adopt-git-unasked before any leg, and _resolved and _answers make their repositories as check's other throwaways do (#1083) #1119, adopt-project.py, shape_materialize.py and tests: a named bad link left out of the copy and pin findings takes no other path's finding with it, and the docstrings and the materializer's refusal name the #979 exception and what helps it (#1089) #1118 and shape_materialize.py and the pinned AGENTS-shape.md: where the root keeps aGNUmakefileor amakefile, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file saysmakereads the first of the three and the block reaches it only once that file holds it (#1048) #1105. adopt-project.py: under a one-byte locale such as Latin-1,checkreads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusingplan-source-gone(#1078) #1106's own head is clean with each as well (RV1153 checked it with all eleven).Closes #1112
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code
Summary by Sourcery
Fix clone-record matching for non-UTF-8 locales so regenerated clones are retained and reused on subsequent checks.
Bug Fixes:
checkfrom repeatedly recloning source repositories under one-byte locales by recognizing equivalent recorded path spellings when deciding whether a newly made clone is at the plan’s recorded location.Enhancements:
Tests: