Repository navigation
adopt-project.py: under a one-byte locale such as Latin-1, check reads 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) - #1106
brettheap wants to merge 2 commits into
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>
Reviewer's GuideFixes Sequence diagram for UTF-8 plan path resolution during checksequenceDiagram
participant Check as adopt-project.py check
participant Source as _recorded_source
participant Resolve as _recorded_path
participant Disk as Filesystem
Check->>Source: _recorded_source(local_path, work_root)
Source->>Resolve: _recorded_path(local_path)
Resolve->>Disk: _is_there(spelling as written)
alt path exists
Disk-->>Resolve: true
else path absent
Resolve->>Disk: _is_there(UTF-8 spelling)
Disk-->>Resolve: true or false
end
Resolve-->>Source: path, there, said
Source-->>Check: accept source or preserve existing refusal
Sequence diagram for retaining a newly cloned sourcesequenceDiagram
participant Check as adopt-project.py check
participant Keep as _keep_the_clone_at_the_record
participant Resolve as _recorded_path
participant Disk as Filesystem
Check->>Keep: _keep_the_clone_at_the_record(made)
Keep->>Resolve: _recorded_path(local_path)
Resolve->>Disk: _is_there(spelling as written)
alt absent or alternate spelling exists
Resolve->>Disk: _is_there(UTF-8 spelling)
end
Resolve-->>Keep: resolved record path
Keep->>Keep: os.path.realpath
Keep-->>Check: retain clone at matching record or leave it for removal
Flow diagram for locale-aware recorded path lookupflowchart TD
A[Recorded local_path] --> B[_recorded_spelling]
B --> C[Ask spelling as written]
C --> D{_is_there?}
D -->|yes| E[Use local spelling]
D -->|no| F[_utf8_spelling]
F --> G{UTF-8 spelling differs and exists?}
G -->|yes| H[Use UTF-8 spelling]
G -->|no| I[Keep local spelling and report existing refusal]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-21T13:09:46Z) Lane 3's independent review of #1106 (RV1106), head aea54cc: NOT READY, for one MUST. The code change itself is sound. MUST
SHOULD
NIT (no change needed)
Checked
Lane 3's plan: the SHOULD is filed as #1112, outside v1.2.0. W1078, the writer, makes the MUST fix as RV1106 tried it (the Lane: openreposhape-3 (openRepoShape-3) |
…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>
|
|
(lane openRepoShape-3, 2026-10-21T14:57:32Z) Lane 3's independent review of #1106, round 2, head 504a1dd. Verdict: READY.
Lane 3's plan: #1106 is READY at 504a1dd. It stays a draft with no milestone and lands after the v1.2.0 tag, on a later word, by the usual pilot, with an explicit squash message. #1112 (at_the_record) starts once #1106 is on main. Lane: openreposhape-3 (openRepoShape-3) |
…py` where GNU make at the root reads a kept `GNUmakefile` or `makefile`, and its NOTE names that file and what it takes for `make bootstrap` to run there (#1047) (#1121) GNU make reads the first it finds of `GNUmakefile`, `makefile` and `Makefile`, and only that one. Where the source keeps `GNUmakefile` or `makefile` in the root, `make bootstrap` at the root of the clone reads the source's file, not the shape's `Makefile`, and exits 2 with "No rule to make target 'bootstrap'". `execute`'s NEXT printed it there anyway. This PR also covers the third box of #1048 (see #1048): the `_next_steps` NOTE that said the shape's targets "arrive only once the follow-up merges the two", which is untrue where GNU make reads a kept `GNUmakefile` or `makefile`. #1105 covers that issue's other two boxes, and leaves this one to this PR. ## Measured GNU Make 4.3 on Linux, a disk that keeps case. One directory per set of files, each file with a `which` target: | files | `make which` reads | `make -n bootstrap` | |---|---|---| | `GNUmakefile` | `GNUmakefile` | exit 2, no rule | | `makefile` | `makefile` | exit 2 | | `Makefile` (with a `bootstrap` rule) | `Makefile` | exit 0 | | `GNUmakefile` + `makefile` | `GNUmakefile` | exit 2 | | `GNUmakefile` + `Makefile` | `GNUmakefile` | exit 2 | | `makefile` + `Makefile` | `makefile` | exit 2 | | all three | `GNUmakefile` | exit 2 | A `GNUmakefile` that holds `include Makefile` runs the shape's rule (exit 0), and so does `make -f Makefile bootstrap`. What NEXT printed on main, from a real `plan`, `check` and `execute --local-remote-dir`, with the printed command run in a recursive clone of the split ("S" is the source's own `scripts/bootstrap.py`, kept with `scripts/`): | the root keeps | NEXT on main | in the clone | |---|---|---| | none | `make bootstrap` | exit 0 | | `Makefile` | `python3 scripts/bootstrap.py`, #480's NOTE | exit 0 | | `makefile`, `GNUmakefile`, or both | `make bootstrap` | **exit 2, no rule** | | `Makefile` + `makefile`, `Makefile` + `GNUmakefile`, all three | `python3 scripts/bootstrap.py`, and a NOTE saying the targets arrive once the two `Makefile`s are merged | the command exits 0; `make bootstrap` exits 2, and still does after `cat shape/Makefile >> Makefile` | | S + `GNUmakefile`, S + `makefile`, S + `Makefile` + `GNUmakefile` | no command; the NOTE says to merge, "and then run `make bootstrap`" | `make bootstrap` exits 2 | ## What changed `adopt-project.py`, `_next_steps`: - It takes `surviving=`, the paths the plan's entries send to the root (`paths_for["root"]`, passed by `cmd_execute`). - It asks them for a kept `GNUmakefile` or `makefile` in GNU make's order, with `_root_keeps`, as `_makefile_follow_up` asks them. So NEXT names the same file as the plan's `CONTRACTS_DIR` follow-up. - Where it finds one, the command is `python3 scripts/bootstrap.py`. The NOTE names the file in the follow-up's words, and says `make bootstrap` and the shape's other make targets run there only once that file has them, or includes a Makefile that does: - the shape's `Makefile` is the root's: "NOTE the shape's Makefile is the root's `Makefile`, but GNU make at the root reads the source's `GNUmakefile`, the first of `GNUmakefile`, `makefile` and `Makefile` it finds, so `make bootstrap` and the shape's other make targets run there only once `GNUmakefile` has them, or includes a Makefile that does; `python3 scripts/bootstrap.py` is what the shape's `make bootstrap` runs." - the source also keeps its own `Makefile`: "NOTE the root keeps its own Makefile and the shape's is shape/Makefile, and GNU make at the root reads ..." (the same clause, and the same ending). - the source's own script went aside too: the merges first, as before, "and then run `python3 scripts/bootstrap.py`; GNU make at the root reads ...". - A root that keeps neither name gets the block it got before, byte for byte. That includes `MAKEFILE`, which GNU make never reads where case is kept. ## Tests `tests/test_adopt_next_make_reads_first.py`, new, 40 tests (38, and the 2 of the fix round below): - 13 rows word for word: none, `Makefile`, `makefile`, `GNUmakefile`, `makefile`+`GNUmakefile`, `Makefile`+`makefile`, `Makefile`+`GNUmakefile`, all three, S, S+`Makefile`, S+`GNUmakefile`, S+`makefile`, S+`Makefile`+`GNUmakefile`. - 3 rows that keep `make bootstrap`: `MAKEFILE`; longer names (`makefile.am`, `Makefile.in`, `GNUmakefile.bak`); and the names below the root. - 13 rows in which NEXT names the same file as `_makefile_follow_up`, for the same root paths. - 9 end to end rows, one for each row that keeps `GNUmakefile` or `makefile`. Each is a real `execute`, and its clone is made by pasting NEXT's own clone line. In that clone: - NEXT's lines are the row's; - `make which` prints the file the NOTE names; - `make -n bootstrap` has no rule; - the printed command exits 0 and ends `bootstrap ok`. For S rows, it does so once the shape's script is merged, as the NOTE says; - where a `Makefile` is kept, the follow-up's merge alone still leaves no rule; - an `include Makefile` in the file GNU make reads makes `make -n bootstrap` run the shape's recipe. On main's `adopt-project.py`, the first 38 all fail. The 9 end to end rows fail on what NEXT prints. The unit rows that guard the unchanged cases fail there only for want of the `surviving=` keyword. M1 below shows the same rows passing and every row that keeps `GNUmakefile` or `makefile` failing. On this head, all 40 pass. Mutants of the change, each restored and checked by sha256. Each run is the new file plus the five `next` tests of `test_adopt_e2e.py` (43 tests). Every mutant is killed: | mutant | failed | |---|---| | M1 `first` never found | 27 | | M2 `makefile` asked before `GNUmakefile` | 6 | | M3 the command ignores `first` | 6 | | M4 the branch for a `Makefile` kept beside `first` dropped | 6 | | M5 the branch for a script gone aside ignores `first` | 9 | | M6 the lookup folds case | 7, two of them existing `test_adopt_e2e.py` rows | | M7 `cmd_execute` passes no paths | 9, the end to end rows | Gate on this head, named files only, one basetemp per run. Each run had `EMAIL` set, `GIT_CONFIG_GLOBAL` an empty file, `GIT_CONFIG_NOSYSTEM=1`, a stub `gh` first on PATH, `GH_TOKEN=bogus` and an empty `GH_CONFIG_DIR`: - the new file, `test_adopt_make_reads_first`, `test_adopt_e2e`: 98 passed, with the new file at 38 tests (at `c04bb710`; the fix round's gate is below). - `test_adopt_github_exits`, `test_adopt_printed_sentences`, `test_repo_hygiene`, `test_readme_windows_quoting_sentence`, `test_cli_reference`, `test_agents_md_codes`: 291 passed. - `test_scaffold_leg_path_collisions`: 104 passed. - `test_adopt_contracts_dir`, `test_adopt_verify`, `test_adopt_pins`, `test_adopt_root_manifest`, `test_adopt_ignored_shape_files`, `test_adopt_nested_attributes`, `test_adopt_shape_pin_and_work_dir`: 378 passed. - `test_shape_doctor.py` in 4 shards: 112 + 112 + 112 + 109 = 445 passed, and 3 skipped (the PowerShell line, which is printed only on a Windows host). ## Fix round 1 (RV1121's SHOULD-1) RV1121 found the code right and one test missing: nothing held `cmd_execute` to handing `_next_steps(surviving=)` only the root's paths. Its mutant X4, which passes `paths_for["root"] + paths_for["code"] + paths_for["drop"]`, kept all 43 tests green. The case is a real one: the `root-project-makefile` rule sends a kept `GNUmakefile` to the root, and its reason says that moving a Makefile that only builds the code to the code leg "is a one-line edit of the plan" (`contracts/path-classification.yaml`, lines 236-237). Two end to end rows, `test_next_keeps_make_bootstrap_where_the_plan_sends_the_makefile_elsewhere[code]` and `[drop]`. Each keeps `GNUmakefile` in the source, edits the plan to send it to `leg: code` (then `leg: drop`), runs a real `execute`, and pastes NEXT's own clone line. Each asserts that NEXT's lines after the clone are ` make bootstrap` and nothing else, that the clone's root has no `GNUmakefile`, and that `make -n bootstrap` there exits 0 and runs `python3 scripts/bootstrap.py`. The `execute`, paste and clone steps of the nine existing end to end rows moved into one helper, `executed_and_cloned`, which the two new rows share, so no block of the test is written twice. Those rows assert what they asserted. The file now holds 40 tests (38 and 2). - **X4, on this head's `adopt-project.py` with the one line changed:** the new file and the five `next` rows of `test_adopt_e2e.py` (45 selected): 2 failed, 43 passed. Both new rows fail on NEXT's lines, which are `python3 scripts/bootstrap.py` and the NOTE "the shape's Makefile is the root's `Makefile`, but GNU make at the root reads the source's `GNUmakefile`, ...", naming a file the root does not have. The file was restored and its sha256 read `60bad59a...` again. - **On main's `adopt-project.py`:** the two rows pass, since main printed `make bootstrap` there. They hold the unchanged case against the new lookup, and X4 is the mutant that tells them apart. - **On this head `1c5173758`**, same environment as above: the new file 40 passed, `test_adopt_e2e.py` 27 passed, `test_repo_hygiene.py` 100 passed. - **Merge:** `git merge-tree --write-tree` of this head with main at `823624d93`: clean (tree `73ddafb2`). RV1121's four NITs are not in this PR; the lane files them. ## Merge - `git merge-tree --write-tree` of this head with main at `6da757cbe`, where the branch starts: clean (tree `21955cc7`). Main has since moved to `7cd66902a`, which changes `scaffold-project.py`, its test and the docs, and none of this branch's files. The merge with it is clean too (tree `ec4e6c8e`). - With #1105's head `5a4de7f76`: clean (tree `3394d19d`). #1105's `adopt-project.py` change is in `_predict_collisions`, apart from `_next_steps`. On that merge, built in a `git clone --shared` checkout, `test_adopt_next_make_reads_first`, #1105's `test_adopt_collision_make_reads` and `test_pinned_copy_text` gave 152 passed. - With the other open pull requests that change `adopt-project.py` (#1120 `c55e55e3`, #1119 `bdcf9952`, #1118 `c60066cb`, #1106 `504a1dd6`, #1091 `160de118`): each clean. ## Not in this PR - Once #1105 lands, the lookup "which of `GNUmakefile` and `makefile` the root keeps first" is written three times: in `_makefile_follow_up`, `_predict_collisions` and `_next_steps`. Each is the same two lines over `GNU_MAKE_READS` and `_root_keeps`. They are kept apart here so this PR and #1105 touch disjoint lines; one helper could serve all three after both land. - `templates/assembly-root/AGENTS-shape.md` line 19 ("`git clone --recurse-submodules` this repository and run `make bootstrap`") has the same gap in an adopted root that keeps `GNUmakefile` or `makefile`. It is pinned, and #1105 changes that file, so it is left. - On a disk that ignores case (#1049), a kept `makefile` is reported as a `Makefile` collision, and NEXT's NOTE says "the root keeps its own Makefile". The new clause is true there too, since GNU make reads that file and merging into it brings the targets. The spelling is #1049's. - A kept directory named `GNUmakefile/` or `makefile/` is named as the source's file. GNU make stops on it with "Is a directory". `_makefile_follow_up` does the same. Closes #1047 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



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.
#1078 (found in the review of #1072).
adopt-project.py checkreadssource.local_pathfrom a plan written in UTF-8. #1072 asks the path as the plan's UTF-8 bytes where the locale cannot spell it (C and POSIX withPYTHONUTF8=0). Under a locale of one byte to a character, Latin-1 say,os.fsencodespells the text, so the path was asked only as written:Thingfollowed by U+00E9 as the one byte 0xE9, where the nameplanread is 0xC3 0xA9.checkrefusedplan-source-gone, "names nothing that exists", of a source that is there. Recorded as a cloneplanmade, it "no longer exists", andgh repo clonewas asked.Following the Direction, a new
_recorded_pathasks the spelling as written first. Where that names nothing, it asks the plan's UTF-8 bytes too, before anything is refused._recorded_sourceand_keep_the_clone_at_the_recordboth use it, so the two read a record the same way._utf8_spellingis factored out of_recorded_spelling, which behaves as before.Decided where the Direction leaves it open:
source-unreadable, a UTF-8 plan reads its source under the C locale, and a member on a branch that is not UTF-8 is put back on it (#918) #1072's, unchanged._is_there), as_recorded_sourcedoes. Astatthe operating system refuses leaves the clone on the list to remove, as anyOSErrorthere did before.localespackage: no/usr/share/i18n, so noen_USsource and no charmaps.localedef -i en_US -f ISO-8859-1fails because the character map file ISO-8859-1 is not found. The test writes the ISO-8859-1 charmap (each byte is the code point of the same number) and a source with onlyLC_CTYPE, then builds it withlocaledef -cin its temporary directory. It needs onlylocaledefand a C library that readsLOCPATH. It skips, quoting localedef's errors, only where a child started in that locale does not reportiso8859-1as both its file name encoding and its preferred encoding.Out of scope
plan-source-gone; ask its UTF-8 bytes too when the first spelling names nothing #1078 has no "Needs a ruling" or "Documented limit" section.planmade,checkclones it again. It then decides whether the new clone sits at the record by comparing it with the spelling as written. Under Latin-1, take a UTF-8 plan whose work directory is not ASCII: the new clone is at the UTF-8 bytes, so it is not taken as the record. It is removed whencheckends instead of being kept, and the next run clones again. That is the clone-again path, outside the Direction, which covers the refusal of a source that is there. 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 follows this up.Measured: the locale, then main and this head
The locale, built with the scratch charmap and source:
The plan was written in UTF-8 mode (
LC_ALL=C PYTHONUTF8=1) for a real repository at<scratch>/repro-*/Thingfollowed by U+00E9. It recordslocal_pathwith that character as the UTF-8 bytes C3 A9. Thencheck --plan, with a stubghthat exits 97:2a119f28REFUSED plan-source-gone: ... names nothing that exists, and the plan does not record it as a clone ...plan okPYTHONUTF8=0plan okplan okPYTHONUTF8=0plan okplan okplan okplan okNOTE ... no longer exists, so its source.repository, testorg/Thing, is cloned again from GitHub, thenREFUSED source-unclonable(gh asked)plan okplan okplan okTests
Each test file ran on its own, one at a time, under a stub
ghthat exits 97, withGH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/nullandEMAIL=tests@openreposhape.invalid. The command was/usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<name> -o tmp_path_retention_policy=failed tests/<file> -rfEs, on Python 3.12.3:tests/test_adopt_one_byte_locale.py(new,-rs):10 passed in 12.65s, none skippedtests/test_adopt_unenterable_source_and_locale.py:13 passed in 13.96stests/test_adopt_unsearchable_source.py:27 passed in 12.45stests/test_adopt_template_values.py:78 passed in 89.48stests/test_adopt_source_clone_record.py:100 passed in 47.47stests/test_adopt_temp_work_roots.py:9 passed in 12.49stests/test_adopt_clone_claimed.py:42 passed in 22.69stests/test_adopt_work_dir_refusal.py:53 passed in 42.06stests/test_adopt_plan_escapes.py:59 passed in 14.54stests/test_adopt_plan_yaml_quoting.py:540 passed in 275.81stests/test_adopt_c_locale_bytes.py:17 passed in 43.96stests/test_scaffold_leg_path_collisions.py:104 passed in 6.14stests/test_agents_md_codes.py:47 passed in 2.34stests/test_cli_reference.py:23 passed in 34.90stests/test_update_add_codes_documented.py:6 passed in 0.49stests/test_repo_hygiene.py:100 passed in 2.90stests/test_no_auto_maintenance.py:58 passed, 2 skipped in 13.30s. Both skips say "the session began with GIT_CONFIG_GLOBAL set": the runner sets it to/dev/null.tests/test_shape_doctor.py, once:445 passed, 3 skipped in 793.00s(the three skips: "the PowerShell line is printed, and powershell.exe exists, only on a Windows host"). A first run was cut off at about 16% when the lane's session stopped; this is the rerun.adopt-project.pyat main and at this head; 0 in the new test file.The new test file:
checkend to end under Latin-1, C and POSIX, for a given record and a cloned one;_recorded_pathunder Latin-1 and C, with a directory at the UTF-8 bytes, at both spellings, or at neither;_keep_the_clone_at_the_recordunder Latin-1.Mutants
Each mutant was applied to
adopt-project.py, and the new test file was run. The file was then restored from a saved copy, andcmpfound it identical every time.4 failed, 6 passed. The failures weretest_a_plan_written_in_utf8_is_read_under_a_one_byte_locale[given]and[cloned],test_the_record_is_asked_as_written_then_as_utf8[utf8-latin1], andtest_the_clone_at_the_record_is_kept_under_a_one_byte_locale.1 failed, 9 passed. The failure wastest_the_record_is_asked_as_written_then_as_utf8[both-latin1]._recorded_sourceback to main's lookup. KILLED:2 failed, 8 passed. The failures weretest_a_plan_written_in_utf8_is_read_under_a_one_byte_locale[given]and[cloned]._keep_the_clone_at_the_recordback to main's spelling. KILLED:1 failed, 9 passed. The failure wastest_the_clone_at_the_record_is_kept_under_a_one_byte_locale.Merges
git merge-tree --write-treeof this head (aea54cc5, based on2a119f28) was run after a fresh fetch, with origin/main at16f9a0e8(#1087):checksays a commit it cannot ask was not asked, and a pinned product YAML 1.1 or 1.2 reads as no text is quoted (#912) #1090 (d5389eb6): cleanchecknames what the clone's.githolds as git keeps refs, and asks a pin's file through the links the root keeps, so a link into.gitor onto a pin's file is refused before any leg or adopted with the copy beside it (#904) #1091 (8f5fd0fd): conflict inadopt-project.py. That PR already conflicts with origin/main in the same file. Both merges have the same 10 conflict hunks, compared hunk by hunk. This PR adds none.Fix round 1 (RV1106)
New head:
504a1dd6, one commit on top ofaea54cc5.The MUST: SonarCloud's duplication gate. The gate failed at "4.5% Duplication on New Code (required <= 3%)". The only duplicated block was the Latin-1 keep-the-clone test's body, a copy of the C test's in
tests/test_adopt_unenterable_source_and_locale.py. Both tests built the record and a clone elsewhere, ranKEEP_THE_CLONEin a child and parsed its answer.kept_clones(tmp_path, env), which returns(spells, left, elsewhere). It sits next toKEEP_THE_CLONEintests/test_adopt_unenterable_source_and_locale.py, so this PR now touches that file too.spellsis False under C and True under Latin-1) and assertsleft == [elsewhere].KEEP_THE_CLONEnow says which tests run it.adopt-project.py.Tests at
504a1dd6, one file at a time, under the same runner as above:tests/test_adopt_one_byte_locale.py(-rs):10 passed in 8.51s, none skippedtests/test_adopt_unenterable_source_and_locale.py:13 passed in 9.53stests/test_adopt_c_locale_bytes.py:17 passed in 40.19stests/test_adopt_one_byte_locale.py, before and after.tests/test_adopt_unenterable_source_and_locale.pyhas 7 before and after, all about itsadopterfixture import, so nothing is new.M4 made again (
_keep_the_clone_at_the_recordback to main's spelling): KILLED,1 failed, 9 passed. The failure wastest_the_clone_at_the_record_is_kept_under_a_one_byte_locale. The file was restored, andcmpfound it identical.Merges of
504a1dd6after a fresh fetch, with origin/main at6da757cb. The open PRs on this PR's three files, checked live, are #1105 and #1091:GNUmakefileor 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 (5a4de7f7): cleanchecknames what the clone's.githolds as git keeps refs, and asks a pin's file through the links the root keeps, so a link into.gitor onto a pin's file is refused before any leg or adopted with the copy beside it (#904) #1091 (8f5fd0fd): conflict inadopt-project.py, the same 10 hunks as adopt-project.py:checknames what the clone's.githolds as git keeps refs, and asks a pin's file through the links the root keeps, so a link into.gitor onto a pin's file is refused before any leg or adopted with the copy beside it (#904) #1091 merged with origin/main. This PR adds none.Closes #1078
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code