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
brettheap wants to merge 2 commits into
Conversation
…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 GuideFix repeated cloning under one-byte locales by recognizing either recorded path spelling when neither path exists, while preserving the established precedence and refusal behavior when the spelling-as-written path is occupied; add focused two-run integration tests for both cases. Sequence diagram for locale-safe clone record detectionsequenceDiagram
participant Check
participant Plan_recorded_source
participant Recorded_paths
participant Filesystem
participant Clone
Check->>Plan_recorded_source: _recorded_source(local_path, work_root)
Plan_recorded_source->>Recorded_paths: _recorded_paths(local_path)
Recorded_paths->>Filesystem: Check spelling as written
Recorded_paths->>Filesystem: Check UTF-8 spelling
Filesystem-->>Recorded_paths: Neither path exists
Plan_recorded_source->>Clone: Source.clone(repository, work_root, unwritable)
Clone-->>Plan_recorded_source: Clone at UTF-8 path
Plan_recorded_source->>Plan_recorded_source: Compare clone real path with each recorded path
Plan_recorded_source-->>Check: Keep clone at the record
Check->>Plan_recorded_source: _recorded_source(local_path, work_root)
Plan_recorded_source-->>Check: Existing clone is reused
File-Level Changes
Possibly linked issues
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) |



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