Repository navigation
bump-leg.py and update-shape.py hand the project's own validators an explicit PYTHONIOENCODING, so a real bump or apply on a project named 日本 is no longer refused as bump-leg-validators-red or update-validators-red under a cp1252 stdout (#254) - #281
Conversation
…explicit PYTHONIOENCODING, so a real bump or `apply` on a project named `日本` is no longer refused as `bump-leg-validators-red` or `update-validators-red` under a cp1252 stdout (#254) A real `bump-leg.py` and `update-shape.py apply` run the project's own `scripts/validate-pins.py` and `scripts/validate-manifest.py` as children. A child takes its stdout configuration from its environment, not from the `reconfigure` its parent did (#214), and the validators are digest-pinned shape copies that print the display name with a plain `print`. Under a stdout whose code page cannot hold it (`PYTHONIOENCODING=cp1252`, or the cp1252 with `surrogateescape` that Windows gives a stdout that is not the console) `validate-manifest.py` raised `UnicodeEncodeError` at `manifest ok: <name>`, exited 1, and the tool refused and rolled back a bump the pins had no quarrel with, naming the validators. - shape_materialize.py: `captured_child_environment()` and `inherited_child_environment()` build the child's environment, and each tool passes it as `env=`. Built-in handler names only: a child given `<encoding>:<a handler only its parent registered>` starts, and then raises `LookupError` at the first character it cannot encode, so #214's `openreposhape.surrogateescape-then-backslashreplace` is never passed on. - bump-leg.py `run_validator` CAPTURES the output. The child is told `utf-8:backslashreplace`, whatever the operator set (their value describes their stdout, and this pipe is the tool's), and the pipe is read with `encoding="utf-8", errors="replace"` instead of the locale's codec, which #217 chose while the child still wrote in it. The words reach the person through the tool's own escaped stdout. - update-shape.py `run_validator` lets the child INHERIT stdout, so it is told the stdout's OWN encoding, with that stdout's handler where the handler never raises (an operator's `replace` still prints `??`) and `backslashreplace` for the rest: `strict` and `surrogateescape` raise on the name. UTF-8 bytes on a cp1252 pipe would be another mistake. - tests/test_git_output_decoding.py: the `run_validator` row leaves EXEMPT, because the call now names both `encoding` and `errors`. Also fixed by the same two changes, and not named in the issue: under `LC_ALL=C PYTHONUTF8=0` (an ASCII stdout) `validate-pins.py` raised on the U+2026 it prints, so a real bump or `apply` was refused there whatever the project was called. Unchanged: the runs under `utf-8:strict`, `utf-8:surrogateescape` and the default stdout of a UTF-8 locale are byte-identical to 60d08a1's for both tools, on a project named `Atlas` and one named `日本`, once the run directory and the shas are masked. One difference, and it is small: a byte of a file name that was not UTF-8, read in as U+DC80..U+DCFF, is written by a validator as the text `\udcff` where the tool writes the byte back; a validator prints one only for a path it lists. Left out: the shape copies a project runs itself. `scripts/bootstrap.py`, `scripts/validate-manifest.py` and the family root's `siblings.py` are digest-pinned and print the display name raw, so `make bootstrap` and `make validate` on a project named `日本` fail under a cp1252 stdout at their own `print`, before any child is run (checked: `bootstrap.py` raises at `bootstrap: <name> (<root>)`). That is a shape change, and it reaches projects through `update-shape.py`. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-11T23:42Z) @codex review |
|
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 13 hours by commenting |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Reviewer's GuideThe PR prevents project validators from failing solely because a child process inherits an incompatible stdout configuration: captured validators are forced to emit and be decoded as UTF-8, while inherited validators receive the actual stdout encoding with a child-known non-raising handler, backed by extensive cross-platform regression tests. Sequence diagram for safe validator output during bump and applysequenceDiagram
participant Tool as bump-leg.py or update-shape.py
participant Validator as Project validator
participant Stdout as Tool stdout
alt Captured validator output
Tool->>Validator: subprocess.run(env=captured_child_environment())
Validator-->>Tool: UTF-8 output
Tool->>Tool: Decode with encoding="utf-8", errors="replace"
Tool->>Stdout: Print escaped validator output
else Inherited validator output
Tool->>Validator: subprocess.run(env=inherited_child_environment())
Validator->>Stdout: Write using stdout encoding and safe handler
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he upstream's committed blob, not its working-tree file, so `apply` on a project named `日本` no longer fails on Windows where the helper that rewrote that file left it CRLF (#254) assert_the_update_landed read `<upstream>/templates/assembly-root/scripts/validate-manifest.py` from the upstream clone's working tree. commit_upstream_changes rewrites that file with Path.write_text, which on Windows turns every \n into \r\n, while the repository's `* text=auto eol=lf` stores the blob as LF. `update-shape.py apply` writes the blob, so the project's copy was LF and the comparison failed in all five encodings of tests-windows on #281. Product code is unchanged: the same comparison now reads `git show <B>:<path>`, as test_update_shape.py already does. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
…s `_entry_findings` composed with #255's, and this PR's pair renamed `unwritable_findings`/`unwritable_remediation` #231 (6f45bf9) puts each entry's path first in `_entry_findings` and returns `unsafe_remediation` ahead of the rest; this branch appends each value `execute` writes into a file that no file can hold. The two hunks are its docstring's order sentence and its return. The sentence keeps main's order and appends this PR's clause; the return is `(findings + submodule_findings + unwritable_findings, unsafe_remediation + remediation + unwritable_remediation)`, so the paths still come first, as #196 needs. This PR's local pair `value_findings, value_remediation` is renamed `unwritable_findings, unwritable_remediation`. #261's branch (ca21b57) binds `value_findings, value_remediation` in the same function, from `_manifest_findings`: a resolution keeping both lines under one name would silently drop one PR's findings. With the rename it cannot. The rest of main (#254/#281, #238/#287, #203/#236, #204/#227, #249/#250, #241, #215 and the earlier merges) merges with no conflict. The resolution is the lane's re-verify one (`resolve-main.py`, 385 passed on 6f45bf9), rename aside. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s `plan-unsafe-path` comes first in the one list of entry findings, and #190's relative urls and NOTEs come last in it, with `execute` still resolving them from the remotes it would create Main since ede15ce: 6f45bf9 (#196 via #231, `entry_path_problem`) and b421e6b (#254 via #281, PYTHONIOENCODING for the validators that bump-leg.py and update-shape.py run). Two files were changed on both sides: adopt-project.py and tests/test_adopt_submodules.py. THREE CONFLICT HUNKS, all in adopt-project.py, and each keeps both sides: 1. Before `_entry_findings`: #196's `NOT_EXTRACTED`, its remediation and `_unsafe_path_findings` are kept whole. `_entry_findings` keeps #190's signature, `local_remote_dir` and the three-tuple with the NOTEs. Its docstring now names #196's order (each entry's path, then coverage, then the legs and mounts, then the submodules) and #190's NOTEs. 2. `_entry_findings`' return: `(findings + submodule_findings, unsafe_remediation + remediation, notes)`. This is #196's remediation first and #190's NOTEs, with `_submodule_findings(plan, source, tree, local_remote_dir)` as #190 calls it. 3. `_refuse_what_check_finds`' docstring: #196's "after the entries' paths, which are the first of these findings", then #190's paragraph on the remotes this run would create. Auto-merged, #196's: the module docstring; `_unsafe_path_findings` and its neighbours; `_extract_leg`; `_mount_the_legs`; `_leg_paths`; the rest of `_refuse_what_check_finds`; and five hunks of tests/test_adopt_submodules.py. Proofs: - Each side's +/- lines are unchanged by the merge, except the two resolved spots (the `_entry_findings` docstring and its return). That holds in adopt-project.py, and tests/test_adopt_submodules.py is identical both ways. - `_entry_findings` is called by `cmd_check` and `_refuse_what_check_finds` (both three-tuples) and by #190's own test, and by no test of main's. - No top-level name is bound twice, and no line over 79 columns is added (42 on main, 42 merged). Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, #242 via #308, #262 and #263 via #313) into fix/adopt-record-submodule-placement Brings in ede15ce (#238), 6f45bf9 (#196), b421e6b (#254), c0ed615 (#242) and 71b58a1 (#262, #263) on top of 6d8d88b. No rebase. ede15ce changes shape-doctor.py. 6f45bf9 and 71b58a1 change adopt-project.py and scripts/shape_materialize.py. b421e6b changes scripts/shape_materialize.py too, giving a project's validators an explicit PYTHONIOENCODING. All of these are files this branch changes. c0ed615 changes scripts/family.py alone. No conflicts. Checked: - adopt-project.py: #196 gives `check` and `execute` one test for an entry's path, `entry_path_problem`, as the finding `plan-unsafe-path`. #262 and #263 add `_root_shape_findings` to `_leg_findings`, for a kept root path where the shape writes. Both reach `execute` through `_refuse_what_check_finds`, before any leg exists and before `_commit_the_split` appends the record. This branch's `submodule_record`, `_resolution_text`, the record's preamble, `_answered_legs`, `_kept_in_a_leg`, `_commit_the_split`, `cmd_execute`, `submodule_plan_problems`, `_submodule_findings`, `Plan`, `y`, `emit` and `walk` are unchanged by the merge. `_root_shape_findings` is asked only beside a `legs:` mapping, so the doctor's placement plan, whose `legs:` is a list, still goes through `_leg_findings(plan, None)`. - shape-doctor.py: #238 changes `validator_row` and `run_validator`. This branch's `check_placement`, `recorded_submodules`, `audit_leg`, `placement_row`, `placement_verdict` and `Context` are unchanged by the merge. - scripts/repo_shape.py is untouched by these commits, so the record's writer and reader, and `double_quoted` in `submodule_record`, are 3e86200's. - Both sides added lines to the module docstrings of adopt-project.py and shape-doctor.py. Every line either side added is in the merge. - No top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py, shape-doctor.py, validate-manifest.py, update-shape.py, scripts/family.py or scripts/bump-leg.py. Each merged module's names are the base's plus what either side added. Every definition only one side changed is that side's text. - AGENTS.md is untouched: 472 lines, under the cap of 472. - The branch's diff against main is still #189 alone: 12 files, +1670 -157, file by file the same as against 6d8d88b. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main brings five commits since 6d8d88b: - ede15ce (#238 via #287): shape-doctor.py's validator rows; - 6f45bf9 (#196 via #231): `entry_path_problem`, one test of an entry's path for `check` and `execute`; - b421e6b (#254 via #281): bump-leg.py and update-shape.py give the validators an explicit PYTHONIOENCODING (scripts/shape_materialize.py gains `captured_child_environment` and `inherited_child_environment`); - c0ed615 (#242 via #308): family.py's `add` and `bump` under excludes; - 71b58a1 (#262, #263 via #313): `check` finds, and `execute` refuses, a kept root path where the shape writes a directory or its `shape/` copy, and the materializer never writes over a copy. It merged with no conflict. Only adopt-project.py changed on both sides. Every other file the merge changes against this branch is main's, and every file it changes against main is this branch's. In adopt-project.py the merge's diff against this branch is main's own diff (559 lines), and its diff against main is this branch's own diff (784 lines). Main's twenty hunks there, by where they land: 1-2. the module docstring: `check` validates each entry's path and keeps no path in the root where the shape writes (6f45bf9, 71b58a1); 3. the `from shape_materialize import` block: `CASE_ASIDE`, `MOVE_THE_LEG`, `first_and_more` (71b58a1); 4-6. `_leg_findings`: a docstring line, the call to `_root_shape_findings`, and the new block after it (`ROOT_BLOCKS_SHAPE`, `ROOT_BLOCKS_COPY`, `_KeptTree`, `_shape_blocks`, `_blocked_sentence`, `_fix_the_kept_path`, `_root_shape_findings`) (71b58a1); 7. after `_submodule_findings`: `NOT_EXTRACTED`, `entry_path_problem`, `_misread`, `_refused_by_policy` and `_unsafe_path_findings` (6f45bf9); 8-11. `_entry_findings`: its docstring, and the entries' path findings first (6f45bf9); 12-13. `_extract_leg`: `literal:` on every `--paths-from-file` line (6f45bf9). This branch changed `_extract_leg` as well, further down: it reads the commit count and the digest before `_push_the_leg`. The two sets of lines do not touch, and the merged function has both; 14-15. `_mount_the_legs`: `git --literal-pathspecs rm` (6f45bf9); 16-17. `_leg_paths`: collected, not judged (6f45bf9); 18-20. `_refuse_what_check_finds`: its docstring and remediation (6f45bf9, 71b58a1). The new findings of 6f45bf9 and 71b58a1 are `_entry_findings`, which `cmd_execute` reaches only through `_refuse_what_check_finds`, before `_create_leg_remotes`. So they are refused before any leg exists, and no notice is due. The materializer's new `materialize-copy-blocked` is raised after both legs are pushed, inside `_commit_the_split`, when a run reaches the materializer without `check`'s finding. That makes it a late failure, which this branch reports with the notice and exit 2. Main's `test_with_check_skipped_execute_stops_at_the_copy_and_overwrites_nothing` expects it to escape `cmd_execute`, so this merge breaks that test, as 9092fda's merge of #200 broke the mount-guard tests. The next commit makes the test read the reported refusal. No top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py or tests/conftest.py. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, #238, #196, #254, #242 and #262/#263 merge with three adopt-project.py hunks kept on both sides, and #262's `_CASE_ASIDE` import kept as the name's one binding origin/main moved b30595c -> 71b58a1: - be2790e (#204 via #227), update-shape.py; - 6d8d88b (#203 via #236), family.py; - ede15ce (#238 via #287), shape-doctor.py; - 6f45bf9 (#196 via #231): adopt-project.py `entry_path_problem`, scripts/shape_materialize.py; - b421e6b (#254 via #281): scripts/shape_materialize.py, bump-leg.py, update-shape.py; - c0ed615 (#242 via #308), family.py; - 71b58a1 (#262 and #263 via #313): adopt-project.py `_root_shape_findings` and scripts/shape_materialize.py `copy_in_the_way`. It fixes what this branch's pull request listed as follow-ups (i) and (ii): a source's own `shape/` where the shape writes its copy, and a plain FILE where the shape needs a directory. Conflicts, all in adopt-project.py, each resolved by keeping both sides: - The module docstring's `check` paragraph. 71b58a1 adds "no path kept in the root where the shape writes ..." before the submodule clause, and this branch adds the symlink clause after it. Both are kept, in that order. - `_entry_findings`' docstring. 6f45bf9 and 71b58a1 give the order "each entry's path, then coverage, then each entry's `leg:`, the two mount paths and what the root keeps where the shape writes, then the source's own submodules". This branch adds "then each symlink the plan keeps in the root that a shape file cannot be written through". - `_entry_findings`' return line. 6f45bf9 returns `unsafe_remediation + remediation`, and this branch appends `_root_symlink_findings(plan, source, tree)` to the findings. The merge returns both. Found after the textual merge, and resolved here so that no commit binds a name twice: 71b58a1 (through 65a9262) imports #198's phrase as `_CASE_ASIDE` in the "#172's shape half" import block, and b37159a had imported it on a line of its own. Main's import is kept; this branch's line and its five-line comment go. scripts/shape_materialize.py merges with no conflict. `place` gets 71b58a1's `copy_in_the_way` inside its collision block and this branch's `_refuse_unwritable` after it, as 71b58a1's message foresaw. Checked on the merged tree: - `_entry_findings` still hands `_leg_findings` the tree, and the symlink findings still come last in its list. - AST: no top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py or scripts/shape_materialize.py, and none is loaded but bound nowhere. Main changed the signature of no function this branch calls. - Both sides are intact by +/- line sets, apart from the three hunks and the import above. The next two commits fix what this merge breaks, each with a revert line. #262's rule and this branch's both name a symlink kept AT the copy under `shape/`. And where the shape's copy lands is now `copy_in_the_way`'s question, asked before this branch's own check of it. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #254 (comment)
What was wrong
A real
bump-leg.py(not--dry-run) andupdate-shape.py applyrun thePROJECT'S own
scripts/validate-pins.pyandscripts/validate-manifest.pyaschild processes after they rewrite the pin. A child takes its stdout
configuration from its environment (
PYTHONIOENCODING, then the locale), notfrom the
reconfigureits parent did in #214, and the validators aredigest-pinned shape copies that print the display name with a plain
print.So on a stdout whose code page cannot hold the name (
PYTHONIOENCODING=cp1252,or the cp1252 with
surrogateescapethat Windows gives any stdout that is notthe console),
validate-manifest.pyraisedUnicodeEncodeErroratmanifest ok: <name>, exited 1, and the tool refused and rolled back a bumpthe pins had no quarrel with. The refusal blamed the validators.
What changed
scripts/shape_materialize.py: two helpers build the child's environment,and each tool passes the one it needs as
env=.captured_child_environment()setsPYTHONIOENCODING=utf-8:backslashreplace.inherited_child_environment(stream=None)sets the stream's OWN canonicalencoding with its own handler where that handler never raises
(
replace,ignore,backslashreplace,xmlcharrefreplace,namereplace), andbackslashreplacefor every other one.scripts/bump-leg.pyrun_validatorCAPTURES the output. It passesenv=captured_child_environment()and reads the pipe withencoding="utf-8", errors="replace", where shape_materialize.run() and 26 other git and gh calls read what the child wrote as UTF-8 whatever the locale, so anadopt-project.py executewhose leg push is rejected with a message naming a non-ASCII path endsREFUSED pushing the spec legand the ruleset hint underLC_ALL=C PYTHONUTF8=0, not a UnicodeDecodeError traceback (#217) #230 haderrors="replace"andno
encoding=.update-shape.pyrun_validatorlets the child INHERIT stdout. It passesenv=inherited_child_environment().tests/test_git_output_decoding.py: thebump-leg.py run_validatorrowleaves
EXEMPT, because that call now names bothencodinganderrors.tests/test_validator_child_encoding.py, 51 new tests.Decisions, and the evidence for each
Captured case: the child writes UTF-8, and the tool reads UTF-8. #230 gave
run_validatorerrors="replace"and noencoding=, "so that codec is theone that reads it back". That was right only while the child wrote in the
locale's codec. Once the child is told UTF-8, the locale's codec misreads it:
'日本'.encode('utf-8')read as ascii is six U+FFFD, and read as cp1252 is'\xe6\u2014\xa5\xe6\u0153\xac'. Soencoding="utf-8"isexplicit. Dropping it fails
test_a_real_bump_reads_the_validators_as_utf_8_whatever_the_locale_is(mutant
bump-decode-with-locale). The words then reach the person throughthe tool's own stdout, which #214 made safe:
manifest ok: \u65e5\u672cona cp1252 stdout,
manifest ok: 日本on UTF-8 and cp932.An operator's
PYTHONIOENCODINGis replaced for the captured pipe andrespected for the inherited one. The operator's value describes the stream
their own words land in. The captured pipe is the tool's, so respecting
cp1252:strictthere would put the failure back one pipe away from theconsole. The inherited fd IS the operator's stream, so the child is told what
that stream is. The tests run under
cp1252:strictandcp1252:surrogateescape(the brief's two), plus
cp1252:replace(an operator's own lenient handler:??before and after),cp932:strict(a Japanese Windows) andutf-8:strict.Inherited case: the stdout's own encoding, never UTF-8, and built-in handler
names only. UTF-8 bytes on a cp1252 pipe would be a different mistake. A
child cannot use a handler only its parent registered:
PYTHONIOENCODING=cp1252:openreposhape.surrogateescape-then-backslashreplacestill STARTS the child and fails with
LookupErrorat the first character itcannot encode.
test_the_handler_a_child_would_not_know_is_never_passed_onruns that child and asserts both halves. At the point the child is built,
main()has already turned astrictstdout intobackslashreplaceand asurrogateescapeone into the registered handler, so the helper mapsstrict,surrogateescape,surrogatepassand the registered name tobackslashreplace, and keeps a handler that never raises.One difference, and it is small: a byte of a file name that was not UTF-8,
read in as U+DC80..U+DCFF, is written by a validator as the text
\udcffwhere the tool writes the byte back. A validator prints one only for a path it
lists.
Evidence
Fails on 60d08a1: with the new test files copied into a throwaway worktree at
60d08a1, 42 failed, 12 passed (the 12 are controls that pass either way:
the five
--dry-runbumps,cp1252:replaceandutf-8:strictfor both tools,cp932:strictforapply, and #230's own two C-locale tests). The decisivelines:
cp1252:strictandcp1252:surrogateescape: exit 2,REFUSED bump-leg-validators-red: scripts/validate-manifest.py exited 1 after the rewrite,UnicodeEncodeError: 'charmap' codec can't encode characters in position 13-14.REFUSED update-validators-red: after the rewrite, scripts/validate-manifest.py refused.test_git_output_decoding.pyflagsrun_validatoronce itsEXEMPT row is gone.
Passes on the head (5757e29):
test_stdout_encoding.py(48),test_bump_leg.py(23),test_update_shape.py(77),test_update_shape_add.py(17),test_git_output_decoding.py(3),test_repo_hygiene.py(50) andtest_cli_reference.py(13): 281 passed, 1 skipped (the existingWindows-only default-stdout test);
test_shape_doctor.py: 181 passed;test_family.pyandtest_update_shape_add_ignored.py: 102 passed;test_update_shape_pr_base.py: 392 passed, 1 skipped.One extra finding, fixed by the same two changes. Under
LC_ALL=C PYTHONUTF8=0(an ASCII stdout; UTF-8 mode has to be switched off by hand, asit is on by default in a C locale)
validate-pins.pyraises on the…(U+2026) it prints, so on 60d08a1 a real bump of a project named
Atlaswasrefused as well: exit 2,
REFUSED bump-leg-validators-red: scripts/validate-pins.py exited 1,UnicodeEncodeError: 'ascii' codec can't encode character '\u2026'. On the head the same run exits 0. This is why theC-locale bump test fails on 60d08a1 at the PINS validator.
Emulation caveat. The
cp932:strictBUMP also fails on 60d08a1 when thetest runs on Linux, but only as an artefact: the child wrote cp932 and a UTF-8
locale cannot read it, where a Japanese Windows' own locale could. It is not
the defect and is not cited as one. The Windows-specific runs here are
emulations (
PYTHONIOENCODING=cp1252:surrogateescape); I did not run anythingon Windows, and these tests first run on Windows in this PR's
tests-windowsjob. The two C-locale tests are POSIX-only (
skipif os.name == "nt") and skipwhere the platform reads UTF-8 under
LC_ALL=C PYTHONUTF8=0.Mutation probes. Eleven hunks reverted one at a time in a throwaway
worktree, the whole new file run each time: all eleven killed.
captured_child_environmentsets nothingcaptured_childunit tests, bumpcp1252:strict,cp1252:surrogateescape,cp932:strict, the C-locale bumputf-8with no handlercaptured_childunit testsbump-leg.pydropsenv=bump-leg.pydropsencoding="utf-8"update-shape.pydropsenv=cp1252:strict,cp1252:surrogateescape, the C-locale apply, the AST guardstrict/surrogateescape/surrogatepassunit and child cases, applycp1252:surrogateescape, the C-locale applybackslashreplaceignore/namereplace/replace/xmlcharrefreplacecases, applycp1252:replacereplacedropped from the never-raises setreplacecases, applycp1252:replaceutf-8ANSI_X3.4-1968,latin-1, the no-stdout casetest_a_stdout_that_is_not_there_leaves_the_environment_aloneByte-identical where nothing was wrong. A real bump and an
apply, from60d08a1 and from the head, on the same project, under
utf-8:strict,utf-8:surrogateescapeand noPYTHONIOENCODING, namedAtlasand named日本: twelve pairs, stdout, stderr and exit code identical once the rundirectory and the shas are masked.
Sibling merge-trees (
git merge-tree --write-tree HEAD <branch>, localtips of lane 5's branches and every open branch that touches
bump-leg.py,update-shape.pyorshape_materialize.py): clean againstfix/yaml-quote-boundaries,fix/yaml-block-key-nul,fix/yaml-flow-mapping-keys,fix/scaffold-referent-chain-links,fix/adopt-check-unsafe-path,fix/adopt-plan-quote-yaml-keywords,fix/adopt-record-submodule-placement,fix/adopt-template-values-utf8,fix/bump-leg-rollback-every-failure,fix/bump-leg-tracked-workflows-only,fix/leg-path-yaml-scalars,fix/update-shape-add-dangling-symlink,fix/update-shape-pr-base-branchandfix/update-shape-pr-create-failure.One conflicts:
fix/adopt-root-kept-symlinks(adopt-project.pyandscripts/shape_materialize.py), and it conflicts the same way with origin/mainitself, so it is not this change.
Left out
scripts/bootstrap.py,scripts/validate-manifest.pyand the family root'ssiblings.pyaredigest-pinned and print the display name raw, so
make bootstrapandmake validateon a project named日本fail under a cp1252 stdout at their ownprint, before any child is run (checked:bootstrap.pyraises atbootstrap: <name> (<root>)). That is a shape change, and it reachesprojects through
update-shape.py.shape-doctor.py'srun_validatoralready setsPYTHONIOENCODING=utf-8for the same validators and reads them as UTF-8, and it is unchanged. A
display name holding a lone surrogate (
name: "a\ud83db") reaches ittoday. The project's own
validate-manifest.pyaccepts that name, thenraises printing it, so
make validateand the doctor fail on a traceback(
INVALID (manifest)). After this change, a real bump andapplyprintmanifest ok: a\ud83dband pass. That is pre-existing, and the fix belongsin the validator or the doctor, not in these two callers: validate-manifest.py accepts a display name holding a lone surrogate (
name: "a\ud83db") and then raisesUnicodeEncodeErrorprinting it, somake validateand shape-doctor.py fail on a traceback instead of a finding while bump-leg.py and update-shape.pyapplypass the same project once #254 lands #280.Review
An adversarial review of 5757e29 found no blocker. Its one should-fix was a
false sentence in this body's "Left out", corrected above and filed as #280.
including UTF-8 mode and the C locale. In each 28-run cell the head had 0
failures; main had 8, or 12 under
LC_ALL=C PYTHONUTF8=0.stderr were identical.
Three nits are kept as they are:
because
PYTHONIOENCODINGsets both streams. With a console stdout and aredirected stderr, the child writes UTF-8 to a cp1252 stderr.
errors="replace"inbump-leg.py'srun_validator. It is defensive.The child now always writes UTF-8, so a
strictmutant survives every test.\udcfftext. This is the difference stated above.Fix round 1 (tests-windows)
The first head, 5757e29, failed
tests-windows: 5 failed and 2477 passed, and macOS and Linux passed. All five failures weretest_an_apply_on_a_project_named_in_japanese_lands_under_any_stdout. Itsassert_the_update_landedcompared the applied copy with the upstream clone's WORKING-TREE file. On Windows that file is CRLF, because the shared test helpercommit_upstream_changesrewrites it with text-modewrite_text. Git stores the blob as LF, which is whatapplywrites. The test now compares with the committed blob (git show <b>:<path>), astest_update_shape.pyalready does, and no product code changed.applycases, and the new one passed them.commit_upstream_changesleaves the upstream clone's working-tree copy CRLF on Windows for the rest of its module. Writing the appended bytes in binary would remove the trap at its source, but the helper is shared and outside this change.Closes #254
🤖 Generated with Claude Code