Repository navigation
adopt-project.py and repo_shape.py: a name that holds a default-ignorable character, which a terminal draws as nothing, is spelled by its code point in plan's lines and in the unsafe-value refusal, so a branch ma<U+034F>in no longer prints as main (#1128) - #1171
brettheap wants to merge 2 commits into
Conversation
…able character, which a terminal draws as nothing, is spelled by its code point in `plan`'s lines and in the `unsafe-value` refusal, so a branch `ma<U+034F>in` no longer prints as `main` (#1128) `_shown` printed a name as it is wherever `str.isprintable` passed it, and that passes 267 of Unicode's Default_Ignorable_Code_Point characters: 263 combining marks, among them U+034F and the variation selectors, and the four Hangul fillers, among them U+3164. `checked_value` named the value it refused with `repr`, which prints the same 267 as they are. - scripts/repo_shape.py: `DEFAULT_IGNORABLE_RANGES`, the property's 17 joined ranges (4174 code points, the same in Unicode 14.0.0, 16.0.0 and 17.0.0), written as a fixed table so that Python 3.12 and 3.14 agree; `DEFAULT_IGNORABLE`; `code_point_escaped`, taken out of `_escaped`, which is unchanged; `visible_repr`; and `checked_value`'s `unsafe-value` refusal names the value with `visible_repr`. - adopt-project.py: `_shown` spells a name that holds one, and `_spelled_character` writes one as its code-point escape, which the plan's reader reads back as the character. - tests/test_adopt_shown_default_ignorables.py (new, 46 rows), with the rows RV1120's review asked for on #1120's `source` line: a printable branch outside ASCII prints as it is, a branch named `main` between backticks is spelled, and the branch that is not UTF-8 is spelled under a `utf-8:surrogateescape` stdout too. - tests/test_adopt_spelled_unambiguous.py: its two sweeps of every character take the default-ignorables apart and hold each to its escape. Stacked on #1120 (see #1035). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR prevents terminal-invisible Unicode Default_Ignorable_Code_Point characters from being displayed raw in adoption plans or unsafe-value diagnostics. It adds a pinned Unicode range table and shared escaping/representation helpers, applies them to plan rendering and refusal messages, and validates all 4,174 code points alongside existing printable, backtick, and surrogate-escape behavior. Sequence diagram for visible unsafe-value diagnosticssequenceDiagram
participant Caller
participant checked_value
participant visible_repr
participant DEFAULT_IGNORABLE
Caller->>checked_value: checked_value(what, value)
checked_value->>visible_repr: visible_repr(text)
visible_repr->>DEFAULT_IGNORABLE: Match default-ignorable characters
DEFAULT_IGNORABLE-->>visible_repr: Matching characters
visible_repr-->>checked_value: repr with code-point escapes
checked_value-->>Caller: Refusal with visible value
Flow diagram for escaping default-ignorable charactersflowchart LR
Name[Branch or path name] --> Shown[_shown]
Shown --> Ignorable{DEFAULT_IGNORABLE match}
Ignorable -->|no| Raw[Display unchanged]
Ignorable -->|yes| Spelled[_spelled_character]
Spelled --> Escape[code_point_escaped]
Escape --> Plan[Plan contains readable code-point escape]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
RV1171: independent review of #1171 at Independent adversarial review for lane 4. The code is right everywhere I measured: the table is Unicode's, and only the 267 characters the issue names change, in every helper. One SHOULD keeps it from READY. The refusal's promise to escape EACH default-ignorable character has no row with two in one value, and a Heads and merge
1. The table
2. The sweep: every code point, #1120's head against this headI ran six outputs for each of the 1114112 code points
3. The refusal's new wording, the readers, and the pinned copy
4. End to end:
|
| Tree | Raw bytes of the character in stdout/stderr (3 surfaces x 3 characters) |
|---|---|
#1120 c55e55e3 |
9 of 9 |
| this head | 0 |
the merge 9de8cbdd9 |
0 |
- The lines on this head, one character each:
source .../probe-pr/U034F/s1 @ a876039c7162 (`ma\u034fin`), 3 files, 1 commits
`ma\u3164in.md` spec medium 1 extension-majority
NOTE the tracking branch is `main`, which is no branch of the source (its branches: `ma\ufe0fin`; the source's default branch is `ma\ufe0fin`). ...
REFUSED unsafe-value: --tracking-branch (not given, so the source's default branch) is 'ma\u034fin', which is not a value this tool will put on a `git` or `gh` command line
- The refusal exits 2 and writes no plan.
- U+200B is spelled on both trees.
checkon those plans prints none of the characters raw.- The plan file still holds them raw (
default_branch: "ma+ U+034F +in"), becausedouble_quotedwrites them. That is validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan teststr.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165's item 3.
5. Mutants
- Setup: each ran on the merge, against the new file,
test_adopt_spelled_unambiguous.pyand 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'stest_adopt_plan_source_branch_shown.py. The baseline was 210 passed. - Restored: each file was restored afterwards, and its sha256 checked.
| Mutant | Result |
|---|---|
| M1: the range U+3164 dropped | killed, 9 failed |
| M2: the range U+E0000-U+E0FFF dropped (holds the printable U+E0100-U+E01EF) | killed, 3 failed |
| M3: a range's end one short, U+FE00-U+FE0E | killed, 9 failed |
| M4: a range's end one past, U+180B-U+1810 (U+1810 is a printable digit) | killed, 1 failed: the table row alone |
| M10: a range's start one past, U+17B5-U+17B5 | killed, 3 failed |
M5: _spelled_character back to raw |
killed, 18 failed |
M6: checked_value back to {text!r} |
killed, 4 failed |
M6b: visible_repr returns repr(str(value)) |
killed, 4 failed |
M7: _shown without the DEFAULT_IGNORABLE test |
killed, 10 failed |
M8: _shown asks .match where it asked .search |
killed, 10 failed |
M9: the escape applied before repr ('ma\\u034fin') |
killed, 7 failed |
| M11: the compiled pattern runs one past each range, the table unchanged | survives, 210 passed |
M12: visible_repr escapes only the first match (count=1) |
survives, 210 passed |
- M12 measured outside the tests:
- this head refuses
mU+034FaU+3164inU+FE0F as'm\u034fa\u3164in\ufe0f'; - M12's form is
'm\u034fa+ U+3164 +in+ U+FE0F +', with two characters raw.
- this head refuses
- M11: this head's compiled pattern equals the table (4174), so M11 is a gap in the tests, not in the code.
6. Merge-trees against the open PRs
Each of the 19 open PRs that touch adopt-project.py or scripts/repo_shape.py merges cleanly (exit 0) with this head and with the scratch merge. The heads fetched are the heads gh pr list gave.
- adopt-project.py:
checkandexecuterun withGIT_NO_LAZY_FETCH=1and refuse a partial-clone source that lacks an object its branches, tags or HEAD reach assource-partial-clone, so neither fetches into the source any more (#1145) #11802c8082a2a, adopt-project.py:plan's table pads each name by the columns a terminal gives it, not the code points it holds, so a wide or combining name'slegcolumn lines up underlegwith every other row's (#1034) #1178fa56168ab, adopt-project.py: the--planpath incheckandexecute, and the--local-remote-dirpath inexecute'sbare, leg andsplitlines, are named with_shown, as #1154 names--outand--work-dir, so a bidi control, an escape, a line feed or a byte that is not UTF-8 in either no longer reaches the terminal as it was typed (#1152) #11775208e874e; - adopt-project.py:
checknames each case twin at the shape's copy, above it and at the manifest, before a link there or after it, and at a pin's file whose exact spelling holds nothing, as_KeptTreekeeps every row, not the first (#1138) #11704df784ac4, adopt-project.py:check's rehearsal and_stored_as_writtentake a parent's-csettings,attr.treetoo, and the source's objects,GIT_ATTR_SOURCEis refused by name before any question, and each throwaway reads its environment first (#1136) #1169b8e63bcf0, 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) #11548a7828332; - adopt-project.py: under a one-byte locale such as Latin-1, a clone
checkmakes 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) #1153ff33ba12a, 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) #11437af5fabc3, 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) #11417a2711d03; - repo_shape.py and bootstrap.py:
COMMIT_RE,SHA256_REandCOMMIT_40end in\Z, not$, so a pin'scommit:ortree_sha256:that ends in a line break is refused as not 40 or 64 hex by every validator andadopt-project.py(#1017) #11264c1f80cf4, 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) #11259549006fe, 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) #11235c3908f8d; - adopt-project.py: an
org/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) #1122c01c4d73d, 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) #11211c5173758, 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) #1120c55e55e33; - 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
bba168bdd, 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) #11182307036ab, 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) #1106504a1dd65, 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) #1105b28f40de0.
Besides #1120 itself, five change a line that names _shown, checked_value or isprintable: #1180, #1178, #1177, #1154 and #1125. None of them changes the helpers. #1180 adds an isprintable() check of its own (N5).
7. Gate
Linux, the landing gate's environment, a stub gh, on 9de8cbdd9.
The files:
- the new file,
test_adopt_spelled_unambiguous,test_pinned_copy_textandtest_stdout_encoding; - every test file that names
unsafe-valueorchecked_value, which is 40 files and includes 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's; test_repo_hygiene,test_path_guard_hygiene(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's rule) andtest_scaffold_leg_path_collisions.
That is 46 files and 5062 ids, in 8 shards. test_shape_doctor.py is 448 ids in 8 shards. Every shard ran from a bash script with mapfile, every list non-empty, every collected id in exactly one shard, no timeout under 600 s.
| Shards | Result |
|---|---|
| g01-g08 (5062 ids) | 4880 passed, 182 skipped, 0 failed |
| d01-d08 (448 ids) | 441 passed, 7 skipped, 0 failed |
the 160 ids those runs skipped because git-filter-repo was not on PATH (156 of the 182 and 4 of the 7), run again with it on PATH |
159 passed, 1 skipped, 0 failed |
- Per shard: g01 614/19s, g02 592/41s, g03 582/51s, g04 590/43s, g05 617/16s, g06 632/1s, g07 622/11s, g08 631/0s (passed/skipped).
- d01-d08 per shard: 56, 56, 56, 56, 56, 52/4s, 56, 53/3s.
- The re-run's one skip is the row's own (
test_leg_path_yaml_scalars.py:581: "the templates read 2026 back ... so the rule has nothing to refuse"). - Net: 5035 of the 5062 gate ids and 445 of the 448 doctor ids passed, 0 failed anywhere. The doctor's 3 skips match the body's. I did not re-run the other 26 gate skips, and their reasons were not recorded (the shards ran with
-rfE).
Findings
S1 (SHOULD): no row puts two default-ignorable characters in one value, so visible_repr's "each" is unpinned (M12 survives).
- What the PR promises: "with each default-ignorable character written as its code-point escape", and the refusal is the surface adopt-project.py: follow-ups from RV1120's review of #1120 (#1035):
_shownlets 267 invisible characters through, and two cases the tests do not pin #1128 names. - What the rows cover: every one holds one character per value: the 4174 sweep, the six characters, the end-to-end rows.
- So a regression goes unseen: a
visible_reprthat escapes only the first (M12,count=1) passes all 210, and it brings adopt-project.py: follow-ups from RV1120's review of #1120 (#1035):_shownlets 267 invisible characters through, and two cases the tests do not pin #1128's defect back for a value holding two. Measured above, its refusal ofmU+034FaU+3164inU+FE0F leaves U+3164 and U+FE0F raw on stderr. - Fix: one row. Give
checked_value(and, for symmetry,_shown) a value holding the three issue characters together. Assert that the refusal holds'm\u034fa\u3164in\ufe0f', that_showngives`m\u034fa\u3164in\ufe0f`, and that neither holds a raw byte of any of the three.
N1 (NIT): the compiled pattern is never checked against the table (M11 survives; M4 dies only on the table row).
- Why the sweeps miss it:
test_adopt_spelled_unambiguous.py's two sweeps take their expected value fromDEFAULT_IGNORABLEitself. A pattern that matches more than the table therefore spells extra characters, and every row still passes. - The cost is small: that over-spells, which is the safe direction.
- Fix: in
test_the_table_is_unicodes, addassert [code for code in range(0x110000) if repo_shape.DEFAULT_IGNORABLE.match(chr(code))] == IGNORABLE. This head passes it (measured: 4174, equal). It fits the S1 round.
N2 (NIT): the body's reason for the shape bump is not so.
- The claim: "a root's
validate-family.pycallschecked_value". - What the file holds:
templates/family-root/scripts/validate-family.pyimports nochecked_value(itsrepo_shapeimport, about :183-188) and names it only in a comment (about :1761). - No pinned copy calls it. Its callers are
adopt-project.py,scaffold-project.py,scripts/bump-leg.py,scripts/family.py,scripts/shape_materialize.py,setup-project.pyandupdate-shape.py, so inside a root the new code never runs. - The placement is still right.
checked_valuelives inrepo_shape.py, so its refusal cannot change without theupstream-changedrow. That makes this the exception to the ruleshape_materialize.pykeeps ("IT LIVES HERE AND NOT INscripts/repo_shape.py", about :1602, :2526, :2980, :4749). - Fix: say that in the body.
N3 (NIT): the skew rule's list does not grow with this PR's names.
NEWER_THAN_A_KEPT_LIBRARY(tests/test_pinned_copy_text.py, about :782) still holds onlyshown_pathandhost_shell_dialect.- A kept root
repo_shape.pyolder than this PR has none ofDEFAULT_IGNORABLE_RANGES,DEFAULT_IGNORABLE,code_point_escapedandvisible_repr. - validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan test
str.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165 is the change most likely to want one in a pinned validator. Add the four here or in validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan teststr.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165, so the skew rule refuses that import.
N4 (NIT, for #1165): two docstrings no longer hold for the 267.
_spelled's "THE SPELLING IS THE PLAN'S OWN": the plan file holds them raw (double_quoted, validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan teststr.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165 item 3), while the spelling escapes them.unwritable_values: it says its value is spelled "asunsafe-valuespells one", but it still uses!r.- Both move with validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan test
str.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165.
N5 (NIT, outside this PR): #1180 adds another member of #1165's class.
- Where: adopt-project.py:
checkandexecuterun withGIT_NO_LAZY_FETCH=1and refuse a partial-clone source that lacks an object its branches, tags or HEAD reach assource-partial-clone, so neither fetches into the source any more (#1145) #1180 (2c8082a2a) adds_refetch, which quotes a remote for a person to paste whereverremote.isprintable() and not remote.startswith("-"). - What happens: a remote named
maU+034Finpasses that test and is printed raw. - Note it on validate-repository-naming.py, validate-manifest.py, validate-pins.py, repo_shape.py, shape_materialize.py and adopt-project.py: the helpers that spell a name for a line or for the plan test
str.isprintable()or userepr, so a combining mark, a variation selector or a Hangul filler in a name prints raw, andmaU+034Finreads asmain#1165 or adopt-project.py:checkandexecuterun withGIT_NO_LAZY_FETCH=1and refuse a partial-clone source that lacks an object its branches, tags or HEAD reach assource-partial-clone, so neither fetches into the source any more (#1145) #1180.
Out of scope, as the brief says
#1165's helpers are untouched by this PR, and I did not review them.
Lane: openreposhape-4 (openRepoShape-4)
…a value that holds three default-ignorable characters is refused and shown with each one escaped, the compiled `DEFAULT_IGNORABLE` is held to the table, and the skew rule knows the four new names (#1128) RV1171's review of #1171 (issuecomment-6356224053), the SHOULD and two of the NITs. - tests/test_adopt_shown_default_ignorables.py (46 rows, now 48): - S1: `m` U+034F `a` U+3164 `in` U+FE0F through `checked_value`'s `unsafe-value` refusal and through `_shown`: each is written as its escape and none is raw. Every other row held one character to a value, so a `visible_repr` that escaped only the first match passed all of them. - N1: the compiled `DEFAULT_IGNORABLE` matches exactly the table's code points over all 0x110000. The sweeps take their expected value from the pattern, so a pattern one past each range passed them. - tests/test_pinned_copy_text.py: `DEFAULT_IGNORABLE_RANGES`, `DEFAULT_IGNORABLE`, `code_point_escaped` and `visible_repr` join `NEWER_THAN_A_KEPT_LIBRARY`, so the skew rule refuses a pinned script that imports one. Stacked on #1120 (see #1035). Test-only. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
RV1171r2: independent re-check of #1171 at Independent adversarial re-check for lane 4, after fix round 1. RV1171's SHOULD (S1) and its NITs N1, N2 and N3 are resolved, and each is measured below. N4 and N5 are left for #1165, as RV1171 said. I found no MUST and no SHOULD. One new NIT (N6) is about tracking, not about the code. Heads and merge
1. Scope of the fix round
2. The four findingsS1 (SHOULD): resolved.
N1 (NIT): resolved.
N2 (NIT): resolved. The PR body now says: "No pinned copy calls
N3 (NIT): resolved.
N4 and N5: left for #1165, as RV1171 said. N4 ( 3. MutantsRun on the PR head (
Both M11 and M12 fail for the right reason. M12's refusal read 4. GateLinux, the landing gate's environment, a stub
That is 496 passed, 1 skipped, 0 failed: W1128c's count, exactly. RV1171's full gate on 5. CI on
|



Closes #1128
Stacked on #1120 (base
fix/1035-source-branch-shown, headc55e55e3): it lands after #1120, and only after the v1.2.0 tag.What was wrong
_shownprinted a name as it is whereverstr.isprintable()passed it, and_spelled_characterwrote such a character as it is.str.isprintable()passes 267 of Unicode's Default_Ignorable_Code_Point characters, which a terminal draws as nothing: 263 combining marks, among them U+034F and the variation selectors, and the four Hangul fillers, among them U+3164. Git takes each in a branch or a path, so a source onma<U+034F>inprinted inplan'ssourceline (#1120's) as(main).checked_value'sunsafe-valuerefusal names the value withrepr, which printed U+3164 raw on stderr the same way. RV1120 found both (#1120 issuecomment-6319321596).What changed
scripts/repo_shape.py:DEFAULT_IGNORABLE_RANGES: the property's 27 lines ofDerivedCoreProperties.txt, joined into 17 ranges, 4174 code points. The table is the same in Unicode 14.0.0, 16.0.0 and 17.0.0, and is written into the code with a citation, not asked ofunicodedata. CI runs Python 3.14 and the bench 3.12, and theirunicodedatadiffer.DEFAULT_IGNORABLE, one character of the table as a pattern.code_point_escaped, taken out of_escaped, which writes exactly what it wrote before.visible_repr:reprwith each default-ignorable character written as its code-point escape.checked_value'sunsafe-valuerefusal names the value withvisible_repr:'ma\u3164in', where it said'ma<U+3164>in'.adopt-project.py:_shownspells a name that holds a default-ignorable character, whateverstr.isprintable()says of it:`ma\u034fin`._spelled_characterwrites one as its code-point escape, which the plan's reader reads back as the character._shown's signature, and its output for every other character, are unchanged, so 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's and 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's rows still pass.scripts/repo_shape.pyis a shape copy that every root digest-pins, so this change shows every project anupstream-changedrow, as every change to it does. The new names are used inside the file itself:checked_valuecallsvisible_repr. No pinned copy callschecked_value(validate-family.pyonly names it in a comment); the change is inscripts/repo_shape.pybecausechecked_valuelives there, so its refusal cannot change without theupstream-changedrow. No pinned script imports a new name, so the skew rule intest_pinned_copy_text.pyholds, and its list of names a keptrepo_shape.pymay lack,NEWER_THAN_A_KEPT_LIBRARY, now holds the four, so it refuses a pinned script that imports one. No sentence in README.md, AGENTS.md or the handbook describes these helpers.Tests
tests/test_adopt_shown_default_ignorables.py(new, 48 rows, ASCII only; every character outside ASCII is written as an escape):DerivedCoreProperties.txt, listed again line by line in the test, so no row depends on the Python's Unicode data.DEFAULT_IGNORABLEagainst the table, over all 0x110000 code points: not one more, not one fewer (RV1171's N1)._shown,_spelled_characterandchecked_value's refusal.mU+034FaU+3164inU+FE0F, throughchecked_value's refusal and_shown: each is written as its escape and none is raw (RV1171's S1)._shown, through a read back of the plan, and end to end throughplan. Inplan, they appear in thesourceline, the tracking-branch NOTE, a path in the table, and theunsafe-valuerefusal on stderr.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'ssourceline (adopt-project.py: follow-ups from RV1120's review of #1120 (#1035):_shownlets 267 invisible characters through, and two cases the tests do not pin #1128 items 2 and 3):feat/caf\u00e9prints as it is;`main`, backticks included, is spelled`\x60main\x60`;`caf\udce9`under autf-8:surrogateescapestdout, where main wrote the byte 0xE9 itself.tests/test_adopt_spelled_unambiguous.py: its two sweeps of every character take the default-ignorables apart and hold each to its escape.tests/test_pinned_copy_text.py:NEWER_THAN_A_KEPT_LIBRARYholds the four new names, so the skew rule refuses a pinned script that imports one (RV1171's N3).Counts for the new file:
961640c68c55e55e3(the 10source-line and item 2-3 rows)_shown,_spelled_characterand the refusal, and the three in one value are raw in both;sourceline, the NOTE, the table and stderr;(`main`)is printed as it is;caf\xe9is on stdout.feat/caf\u00e9.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 alone, the 3 failures are U+034F, U+3164 and U+FE0F in thesourceline. Items 2 and 3's three rows pass on 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 alone: they pin 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's code, which is right in each case. They kill RV1120's mutants E (_spelledfor every branch outside ASCII) and N (str.isprintable()alone, with no backtick rule).Sweep
Run with
/usr/bin/python33.12.3 (Unicode 15.0.0), against #1120's headc55e55e3, whose helpers are main's:str.isprintable()(263 Mn, 4 Lo). Raw on the base: 267. Raw on this head: 0._shown,_spelled_characterandvisible_reprgive the same output as the base's_shown,_spelled_characterandrepr: 0 differ.Mutants
Each ran against the new file,
test_adopt_spelled_unambiguous.pyand #1120'stest_adopt_plan_source_branch_shown.py(212 rows). Each file was restored afterwards and its sha256 checked._spelled_characterchecked_valueback onreprvisible_represcapes only the first match (count=1)validate-family.py) importsvisible_reprvalidate-familyrow); passes on the old listGate
On a scratch merge commit of
origin/main038dafba9, #1120'sc55e55e3and this head (39cb15180), in the landing gate's environment with a stubgh:_shown,_spelledorunsafe-value, withtest_tracking_branch_ref.py,test_pinned_copy_text.py,test_adopt_plan_escapes.py,test_stdout_encoding.py,test_non_utf8_name_callers.py,test_path_guard_hygiene.py,test_repo_hygiene.pyandtest_scaffold_leg_path_collisions.py. 9777 passed, 29 skipped, 0 failed.test_shape_doctor.py, 448 ids in 8 shards frommapfilelists: 445 passed, 3 skipped, 0 failed.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, this head and 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, the rows of 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, of 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 and of this PR: 166 passed, 1 skipped.331dc93c7oforigin/main961640c68and8162b874e,test_adopt_shown_default_ignorables.py,test_adopt_spelled_unambiguous.py,test_pinned_copy_text.py,test_adopt_plan_source_branch_shown.py,test_stdout_encoding.py,test_path_guard_hygiene.pyandtest_repo_hygiene.py: 496 passed, 1 skipped, 0 failed.Merge-trees
git merge-treeof this head withorigin/mainis clean. So is each of the 13 other open PRs that touchadopt-project.pyorscripts/repo_shape.py, against both this head and the scratch merge: #1154, #1153, #1143, #1141, #1126, #1125, #1123, #1122, #1121, #1119, #1118, #1106 and #1105.Fix round 1:
git merge-treeof8162b874ewithorigin/mainand with #1105 (the one other open PR that touchestests/test_pinned_copy_text.py) is clean, and so is the scratch merge with #1105.Out of scope: filed separately by the lane
The other helpers that test only
str.isprintable(), or userepr, let the same characters through. They are:repr-based_showncopies invalidate-repository-naming.py,validate-manifest.pyandvalidate-pins.py, andrepo_shape.shown_path;shape_materialize.py's--leadingunsafe-valuerefusal;repo_shape.double_quoted, which writes them raw into the plan file;adopt-project.py's_worded,_line_shown,unwritable_valuesand_refuse_an_unwritable_clone_url.They are filed separately by the lane as #1165 (see #1165), and are untouched here.
🤖 Generated with Claude Code
Lane: openreposhape-4 (openRepoShape-4)
Summary by Sourcery
Make terminal-invisible Unicode characters unambiguous wherever branch and path names are displayed or refused.
New Features:
Bug Fixes:
Enhancements:
Tests: