Repository navigation
setup.sh, install.sh and openRepoShape: a ref git refuses part by part (a.lock/b, a/.b, a//b, a/b/, a.) is refused by each of them before the clone, the fetch or the record, and each refusal states git's rule - #875
Conversation
…IR` is respelled with `cygpath -u` or refused before install.sh's fetch, and the PATH note single-quotes a directory bash would still read, and `--install` refuses one holding `:` or a newline, so install.sh names `/d/tools/bin` as the command does and the `export PATH` line does what it says (#667) #649's two leftovers, both from #656's review. install.sh read `$OPENREPOSHAPE_BIN_DIR` as typed, so with `D:\tools\bin` its `preflight:` and `done:` lines (and `install:` for an old release) said `D:\tools\bin/openRepoShape` while the command's own lines said `/d/tools/bin/openRepoShape`, and where no `cygpath` answers the command's `--install` refused (exit 2) only after install.sh had fetched it. install.sh now carries a copy of the command's rule where it reads the variable, before the symlink check and the fetch: a value that starts `[A-Za-z]:/` or `[A-Za-z]:\` is spelled with `cygpath -u --`, and is refused with exit 2 where no `cygpath` answers, fails, or answers a drive spelling again. The refusal does not echo the value. The command's PATH note printed `export PATH="$dir:$PATH"` with the directory between double quotes as it was, so `a"b$HOME c` printed a line `eval` could not parse, and a `$`, a backquote or a `\` in a name was read, not kept. `export_path_line` keeps today's line byte for byte for a directory that double quotes hold (a space and an apostrophe included) and, for one holding a `"`, `$`, backquote, `\` or `!` (bash reads that at a prompt as history), prints it between single quotes with `'\''` for an apostrophe. `--install` also refuses a directory holding a `:` or a newline, which no `$PATH` line can name, before anything is placed; the newline's refusal does not echo it. Tests feed one value through install.sh and through the command, in each spelling and each state of `cygpath`, and pin every `install:`, `preflight:` and `done:` line; the PATH note is checked as printed, by `eval` and by `bash -i`. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…holding a control character before they name it, and install.sh refuses a `:` before its fetch, so a carriage return or an escape is no longer echoed as typed (#760) #667's two checklist candidates, with rv667's NIT 2. `--install` refused a newline in `$OPENREPOSHAPE_BIN_DIR` and nothing else that is a control character: a carriage return, an escape, a tab or DEL placed the command and was printed as typed (`/tmp/x^MFORGED is not on $PATH`; an escape sequence reaches the terminal the same way). The newline arm is now `*[[:cntrl:]]*`, first in `install_commands`, ahead of the Windows spelling and the colon, and its refusal still says nothing of the value. It is `[[:cntrl:]]` and not `[![:print:]]`, so a directory with a letter the locale does not print (`café` under LC_ALL=C) is placed. Which C1 controls match is the locale's, measured with bash 5.2.21: C.UTF-8 matches U+0080 to U+009F (and U+2028, U+2029), C and POSIX match none, and the ASCII controls and DEL match in all three. install.sh learnt of a `:` or a newline only after its fetch: it fetched the launcher, ran `--install`, and said only that `--install` refused (exit 2); a release older than #667 placed the command under the directory. It now carries both arms of the command's `case`, copied as #667 copied the drive-letter one: the control-character arm first, before any line names the directory, and the colon arm after the Windows respelling, both before the symlink check and any request. Where the control-character refusal says nothing of the value, the colon refusal names the directory, as the command's does. Tests feed each value through install.sh and through the command (a colon, two directories, a drive-relative name, a newline, a carriage return, an escape, a tab, DEL, a drive spelling with a newline or an escape, a colon with an escape): exit 2, no request, nothing placed, neither stream holds a forged tail. A link in a directory holding a carriage return, a drive spelling, a colon with an escape, a letter under LC_ALL=C and LC_ALL=C.UTF-8, a C1 control under a UTF-8 locale, and the `$HOME` default pin the order and the width of the arms. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…`LC_ALL=C` and names a C1 control's two bytes, so U+0080 to U+009F is refused in every locale and whatever else the value holds, where a UTF-8 locale let one through beside a byte that is not UTF-8 (#760) Lane 1's review of #804 (finding 1): `*[[:cntrl:]]*` matches a C1 control only in a value that is valid UTF-8. One byte that is not (a lone 0xE9) makes bash match byte by byte, and then U+009B's two bytes (0xC2 0x9B) are not controls, as under `C`. Measured on 7569ffa with bash 5.2.21: under `LC_ALL=C.UTF-8` and `$OPENREPOSHAPE_BIN_DIR` set to `<dir>/caf`, 0xE9, U+009B, `31mFORGED/bin`, `openRepoShape --install` exited 0, placed the command and printed the value raw in four lines; install.sh, given the same value after a `:`, exited 2 with the colon refusal naming it, the C1 control in the line. Both files now run the check as `(LC_ALL=C; case ... *[[:cntrl:]]* | *$'\302'[$'\200'-$'\237']* ...)`: the ASCII controls and DEL byte by byte, and the two bytes of each C1 control named, so the answer is the same in every locale. Every value measured on 7569ffa and refused only under C.UTF-8, or not at all, is refused with exit 2, nothing placed, nothing echoed, and install.sh makes no request. `café` and U+00A0 are still placed. U+2028 and U+2029, which only a UTF-8 locale's `[[:cntrl:]]` matched, are now placed in every locale, and a test says so. Both comments say what the check does, and no longer say the C1 controls are the locale's. Tests: the C1 case, which skipped where no UTF-8 locale read U+009B as a control, is now five values (U+009B, the first and the last of the range, a 0xE9 byte before U+009B, and a colon beside that) in each of `C` and `C.UTF-8`, through the command and through install.sh. The placed-letter test gains U+00A0 (the byte after the range's last), U+2028 and U+2029. 18 of the 36 fail on 7569ffa's code and the other 18 are guards; 18 mutants of the two code hunks are caught. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he stacked base of #837
…holding a bidi control, a character of no width, a soft hyphen or U+2028 before any request and name it by its code point, so no line about `$PATH` shows the directory as another (#837) #804's check refuses the C0 and C1 controls and DEL, and passed every character a terminal draws as something other than itself, under `C` and `C.UTF-8` alike: the command placed itself under `<dir>/a` U+202E `forged/bin` and printed the value in four lines, and install.sh made its request. Laid out as a terminal lays out a line (fribidi), `~/` U+202E `.local/bin` reads `~/nib/lacol.`, `/opt/tools-1` U+200F `/2/bin` reads `/opt/tools-12//bin`, and `~/.local` U+200B `/bin` reads `~/.local/bin`. Both files now refuse, right after the control check, before the drive spelling, the colon, the symlink check and any request, a directory holding one of 29 code points, `MISDRAWN_CHARACTERS` in the launcher: U+00AD; the twelve bidirectional controls (U+061C, U+200E, U+200F, U+202A to U+202E, U+2066 to U+2069), as `repo_shape.spelled` escapes them (#391); U+2028 and U+2029, which end a line for every reader that keeps Unicode's line breaks, and which #804 left to a ruling; and U+200B, U+FEFF and U+2060 to U+206F, of no width, the whole block default-ignorable. U+200C and U+200D stay allowed: Persian, the Indic scripts and emoji sequences need them. So does every space. `misdrawn_characters` builds each one's UTF-8 from its code point with printf's octal escapes and looks for the bytes under `LC_ALL=C`, in the subshell #804's check uses, so one list says what is refused and what the refusal names, and no byte is read back as a number. The refusal says "holds a character a terminal draws as something other than itself (U+202E): one that is invisible, ends a line or reorders the text around it, so no line about $PATH can print the name as it reads", names every one found, and nothing else of the value. install.sh carries the list and the function as the launcher has them, and a test holds the two to one text. Tests: #804's C1 locale test in each file now runs `MISDRAWN_BIN_DIRS` too (each code point, two at once, after a byte that is not UTF-8, beside a colon, in a drive spelling), under `C` and `C.UTF-8`, through the command and install.sh, with no request; the link test takes U+202E; the lifted function names exactly its 29 over every code point that shares a first byte with them (896, on stdin, so it runs under Git Bash); `NOT_CONTROLS` drops U+2028 and U+2029 and gains a Persian word with U+200C, an emoji sequence with U+200D, U+2026 and U+FF21. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion and the redirect's location are refused unechoed for a misdrawn character too, the set takes in U+FFF9 to U+FFFB, U+1D173 to U+1D17A and U+E0001, and the refusal says a character may be drawn as another (#837) Fix round 1 on rv841's review of #841. The set (SHOULD 1). `MISDRAWN_CHARACTERS` gains FFF9-FFFB (the interlinear annotation characters), 1D173-1D17A (the musical beam, tie, slur and phrase controls) and E0001 (the deprecated language tag): format characters of no width (glibc `wcwidth` 0) that nothing needs, which the command placed and printed in four lines. The encoder builds the four-byte form too, with bash 3.2's arithmetic and `%b`'s `\0NNN`. Every other format character is left out for a reason the comment now gives: U+200C, U+200D and U+180E, which Persian, the Indic scripts, emoji and Mongolian need; the Egyptian hieroglyph and shorthand format controls, with which those scripts build their signs; the tags, the variation selectors, U+034F and U+1160, drawn as part of the character before them; the signs drawn over the digits after them, which take a cell; and every space and the blank Hangul fillers. The repository and the ref (SHOULD 2). #837's line named the `[![:print:]]` checks of both files: a UTF-8 locale's U+202E, U+200B, U+FEFF or U+00AD passes them, and the refusal after them echoed the value. Each now asks `misdrawn_characters` first, and refuses by the code point, saying nothing else of the value: `checked_repo`, `checked_version` (the version named and the newest release's tag), the option check and `latest_from_redirect`'s location in install.sh; `shape_checkout` and `ref_fault` (`$OPENREPOSHAPE_REF` and the record) in the command. First, because `[![:print:]]` is the locale's, so the words do not change with it. The words (NIT 3): "one that is invisible, is drawn as another character, ends a line or reorders the text around it", because U+00AD is drawn as a hyphen. The order (NIT 5): a tab before U+202E is refused as a control character, and a row holds it. Tests: `MISDRAWN` and the locale table take the twelve new code points and the tab row; the lifted sweep takes U+1D100 to U+1D1FF, U+E0000 to U+E007F and a sample of the planes past U+FFFF; `NOT_CONTROLS` takes a subdivision flag (tags U+E0020 to U+E007F); a test of each file refuses a value holding U+202E at every new site under `C` and `C.UTF-8`, through both entry points, with no request, nothing placed and neither stream holding it. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e stacked base of #858
…fuse is read again in the locale's own charset, so a GB18030, GBK or Shift_JIS name that holds no control character and nothing drawn as another is placed, and where nothing can read it the bytes decide as before (#858) #804's control check and #837's `misdrawn_characters` look for UTF-8's bytes under `LC_ALL=C`, and a multibyte charset that is not UTF-8 reads some of those bytes as text: GBK, GB18030 and CP936 read `0xC2 0x80` to `0xC2 0x9F` as 32 hanzi; GBK, Big5, EUC-JP and EUC-KR read U+00AD's two bytes as one character; and in Shift_JIS a kanji whose second byte is `0xC2` before a common kanji puts a C1 control's two bytes side by side (390 in 100,000 random pairs of common kanji, measured with Python's codecs). Such a directory, the default `$HOME/.local/bin` under a user name among them, was refused as holding a control character or a character drawn as another, in a locale where it holds neither. Both files now carry `locale_reading` (install.sh as a copy, held to one text by a test). Where the bytes refuse a directory, it asks `locale charmap` under the person's own locale, and where the answer is neither empty, UTF-8 nor ASCII, `iconv` reads the whole value from that charset into UTF-8; the reading is used only where it has fewer characters than the value has bytes, so a single-byte charset keeps the byte rule, as the issue's Direction says. The C1 bytes and `misdrawn_characters` are then asked of the reading, and `[[:cntrl:]]` of the bytes as typed. Only a value the bytes refuse is read again, so no name placed before is refused; with no `locale` or `iconv`, a charset either does not know, or a value the charset cannot read, the bytes decide, in #804's and #837's words. A lone `0x9B` stays placed, the limit #804 states: a terminal that reads UTF-8 draws it as a character it cannot read, only one set to an 8-bit charset acts on it, and a rule needs a UTF-8 validity step bash does not have. The launcher's comment says so. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…above) # Conflicts: # install.sh # openRepoShape # tests/test_install_sh.py # tests/test_openreposhape_command.py
…cked once, before `--version` answers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent (#866) `--version` printed both values as typed and exited 0. A scaffold run sent its fetch with them, and so did `--install` from stdin, and each named them three times in its refusal. So an escape, a C1 control, U+202E or U+200B in either value reached the terminal as it was typed. One function, `refuse_bad_values`, now runs once the arguments are read, and inside the `--version` case, which answers within the argument loop. It refuses the broken record first, as before. Then it checks `$OPENREPOSHAPE_REPO` with `checked_repo`, `shape_checkout`'s guard moved whole: #841's misdrawn characters, then a leading `-` or `[![:print:]]` on the clone URL. Then it checks `$OPENREPOSHAPE_REF` with `ref_character_fault`, `ref_fault`'s misdrawn and control-character checks, moved whole. `ref_fault` calls them in the same place. Every refusal keeps the words it had. The ref's length and shape stay where a ref is used: `checked_ref` when `--install` records, `shape_checkout` before `git checkout`, and setup.sh's `--shape-ref` checks. So `--version` still prints any printable ref in force, and an install that records no ref still takes one that names nothing, as workBenches sets it. `--version` refuses with exit 2, as a broken record already makes it do, and does not respell the value. install.sh's `shown=` parse is unchanged for every value it can hand the command: an owner/name that its own `checked_repo` passed, and the tag that its `checked_version` passed. Both pass this check, and `--version` prints them byte for byte. `--help` still answers first. It names the variables, never their values. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_BIN_DIR` must write back into the value byte for byte, and tests hold that the reading is itself checked, that `locale charmap` is asked under the person's own locale and that a name the bytes pass is never read again (#858) Fix round 1 on rv858's review of #867 (READY, 2 SHOULDs, 5 NITs). SHOULD 1: no test had a multibyte charset read a name whose reading still holds a C1 control or a character drawn as another, so `locale_reading` printing anything at all (rv858's R1) shipped green. Two rows now do, through both files: a GB18030 hanzi of the C1 bytes beside U+009B in GB18030's own four-byte form (`0x81 0x30 0x83 0x37`), refused as a control character, and beside U+202E (`0x81 0x36 0xA7 0x31`), refused naming U+202E, install.sh before any request. SHOULD 2: the stub `locale` answered one charmap whatever it was asked under, so `LC_ALL=C locale charmap` (R2) shipped green. `put_a_locale`'s stub now answers as glibc's does, from the first of `$LC_ALL`, `$LC_CTYPE` and `$LANG` set: a marker `xx_XX.<charmap>` gives that charmap, `C.UTF-8` gives UTF-8, anything else `ANSI_X3.4-1968`; the cases run with `LANG` the marker and `LC_ALL` and `LC_CTYPE` empty, so bash runs in `C` (it says nothing of a `LANG` that is not installed). `C` and `C.UTF-8` stay where they mean something: as the person's own locale, in a test of three names refused there with `iconv` never asked; and as bash's own locale, in a test of `locale_reading` lifted out of each file with a `locale` function, `C.UTF-8` standing in for a multibyte locale of bash's own, where the readings are Python's codecs' and the count stays in bytes. NIT 3: a name the bytes pass (`café`) under a GB18030 locale, with an `iconv` that leaves a mark if it runs: placed, no mark (R3, the trigger replaced by `if :; then`). NIT 4: an `iconv` that exits 0 and prints nothing passed the count. The reading must now write back into the charset as the value, byte for byte (`iconv -f UTF-8 -t <charmap>`), in both copies; a broken `iconv` falls back to today's refusal, and a test holds it. The rows where `iconv` must not be asked use the marking `iconv` instead of one that reads anything, which the round trip would refuse anyway. NIT 5: `locale_reading`'s comment, both callers' and `export_path_line`'s say the trade: a multibyte locale that is not UTF-8, in a terminal that reads UTF-8, is handed C1 bytes it reads as letters. NIT 7: the launcher's comment names the ISO 8859 case (the charset is known and `a`, `0x9B`, `b` is placed) as a candidate, left. NIT 6 is the PR body's. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…BIN_DIR` and then fails gives no reading, so the first `iconv`'s exit is pinned beside the round trip (#858) Fix round 1 on rv858's review of #867, its last step. With the round trip in `locale_reading`, dropping the first `iconv`'s `|| return 1` (F07) survived the mutant table: a failing `iconv` that printed part of a reading, or nothing, is refused by the round trip anyway. One that prints the whole reading and then exits 1 is not: the round trip passes. A row of `REFUSED_AS_THE_BYTES_SAY` now has such an `iconv` (`READS_THEN_FAILS`, this machine's run in full, then `exit 1`) read a hanzi of the C1 bytes under GB18030, through both files: refused as a control character, as the bytes say, and install.sh before any request. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…O` that ends in a newline is refused too, and the order of the up-front refusals is pinned (#866) Fix round 1 on #872, after rv866's review. SHOULD-1: `checked_repo` looked at `url="$(shape_clone_url)"`, and `$(…)` drops the newlines at the end of what it captures. So a URL, an scp address or a path ending in a newline passed the check. `--version` then printed it, and a scaffold run sent it in both requests. An owner/name keeps the newline inside its URL, before `.git`, so it was refused already. The misdrawn characters are now looked for in `$REPO` as typed, and the case reads `"$url$REPO"`: the leading `-` is still looked for in the URL's first character, so `-acme/r`, which becomes `https://github.com/-acme/r.git`, still passes, and a control character anywhere in either is refused. The words are unchanged. NIT-1: a new test holds the order `refuse_bad_values` states. With a broken record and a bad repository, the record is refused. With a bad repository and a bad ref, the repository is refused. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tacked base of #874
…t (`a.lock/b`, `a/.b`, `a//b`, `a/b/`, `a.`) is refused by each of them before the clone, the fetch or the record, and each refusal states git's rule (#874) Each of the three kept the whole-name rule `*..* | *.lock`, which git does not stop at: it asks each part between slashes. `a.lock/b`, `a/.b`, `a//b`, `a/b/` and `a.` passed all three, so `setup.sh --shape-ref a.lock/b` printed `(0) self-bootstrap`, cloned the standard and ended in git's `pathspec 'a.lock/b' did not match`, `install.sh a.lock/b` asked the network for `.../a.lock/b/openRepoShape`, and `openRepoShape --install` with the opt-in wrote `a.lock/b` to the record every later fetch reads. Each check gates something real (setup.sh: the clone, on the self-bootstrap path only; install.sh: the fetch, the version being the URL's path; openRepoShape: the record an install writes and a run reads, and the doctor's checkout), so each holds git's rule itself instead of leaving it to a later setup-project.py run. The same nine patterns in all three, in plain `case` patterns bash 3.2 reads, inside the character set each had: a part after a `/` that starts with `.`, a part before a `/` that ends in `.lock`, an empty part, a `/` at the end, and a name that ends in `.`, with `..` and the `.lock` ending as before. Over every name of up to five parts from `a`, `0`, `.`, `/`, `-` and `.lock` that the character set takes (3110), the old line disagreed with `git check-ref-format --branch` on 1122 and the new one disagrees on none (git 2.43.0). Each refusal's sentence now states the whole rule, in setup-project.py's words where they fit (#857's `why_not_a_ref`), and setup.sh's comment says it mirrors `checked_ref`, which asks `REF_RE` and then `why_not_a_ref`. Tests: one table of names in tests/conftest.py (the issue's, rv873's, and the refs that must still pass), fed to each copy and to git, which must answer alike, one test in each of test_setup_sh.py, test_install_sh.py and test_openreposhape_command.py. All offline; git is asked, never copied. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…able tests of #874's ref rule run under a `gh` and a `curl` that log and fail, and a refused ref asks neither, so the real network stays out of reach in the three of them as it did in install.sh's (#874) The install.sh table test already ran under that file's fakes, which log every call and fail what they do not serve, and asserts that a refused version logs none. The setup.sh and openRepoShape ones were offline only by construction (a path nothing is at; `--install` fetching nothing), so a change that made either reach for `gh` or `curl` would have passed. Both now put `fake_github(tmp_path, (), log=...)` first on `$PATH`: a `gh` that serves nothing and a `curl` that refuses, each appending its arguments to a log. The setup.sh one asserts the log was never written; the launcher one asserts that no `curl` ran and no `gh` but the `gh auth status` the machine report of an install that placed the command asks, and that a refused install asked for nothing at all. `install_at` takes an `env_also` for it. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR updates setup.sh, install.sh, and openRepoShape to enforce Git's branch-ref rules part by part, with consistent explanatory refusals and pre-side-effect validation, and adds shared parameterized tests that compare all three copies against the Git executable. Sequence diagram for Git-compatible ref validation before side effectssequenceDiagram
participant User
participant Script as setup.sh_or_install.sh
participant GitOrCurl as Git_clone_or_curl
participant Record as openRepoShape_record
User->>Script: provide ref
Script->>Script: checked_ref_or_checked_version
alt ref violates Git's part-by-part rules
Script-->>User: refuse with Git rule
Note over Script,GitOrCurl: No clone, fetch, or record
else ref accepted
alt setup.sh
Script->>GitOrCurl: git clone
else install.sh
Script->>GitOrCurl: curl version URL
else openRepoShape --install
Script->>Record: write openRepoShape.ref
end
end
Flow diagram for the shared Git ref ruleflowchart TD
A[Ref input] --> B{Matches any Git-invalid part pattern?}
B -->|Yes| C[Refuse with explanatory rule]
B -->|No| D[Continue to clone, fetch, or record]
E[Invalid patterns] --> E1[Starts with . after /]
E --> E2[Part ends in .lock]
E --> E3[Empty part between slashes]
E --> E4[Name ends in .]
E --> E5[Contains ..]
E1 --> B
E2 --> B
E3 --> B
E4 --> B
E5 --> B
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-2, 2026-10-18T05:38:08Z) Independent review of #875 (#874) at No MUST. The change does what the issue asks. In each of the three copies it refuses exactly what Next: lane 2's writer takes SHOULD 1 and NITs 3 to 7 in a fix round. SHOULD 2 and NIT 8 go to Brett Heap as candidates; nothing is filed without his word. SHOULD
NIT
Checked and true
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
…le's sentence says ASCII letters as setup-project.py's does, three rows hold the patterns off a part that starts upper-case, `_` or `-`, and two comments say what is true (#874) rv874's review of #875 (READY, no MUST), fix round 1. The shared sentence said "a letter or digit" and "letters", which `Café` satisfies under a UTF-8 locale although the patterns refuse it; #873's REF_RE words say "an ASCII letter or digit" and "ASCII letters" (#777, rv811). The three copies' refusals and `REF_RULE_WORDS` now say the same, byte for byte, and so does setup.sh's comment. `REF_PARTS` had no part that starts upper-case, `_` or `-`, so three over-refusing mutants passed all 33 rows of their copy's table test: `*/[-_]*` in setup.sh, `*/[A-Z]*` in install.sh and `[!a-z0-9]*` for `[!A-Za-z0-9]*` in openRepoShape. `Feature/ABC-123`, `fix/_tmp` and `fix/-wip` join the names that must pass, and each mutant is killed by them. `a/b.` and `a/.` move up to the names the old line took, where the docstring already counted them, from "refused before #874 as well". setup.sh's comment names `why_not_a_ref` as #857's, held here since #874, and says each rule has one or two patterns (an empty part and a `.lock` part have two). install.sh's note says a refused version is refused before the launcher is fetched: with no version named, `releases/latest` is asked first. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-2, 2026-10-18T06:13:13Z) Re-verification of #875 (#874)'s fix round 1 at Every finding from the first review is fixed as asked, or left as a candidate for Brett Heap as agreed. All runs are green. The fix round's code and tests are right. There is one new SHOULD and one NIT, both in text outside the code. SHOULD
NIT
Checked and true
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
|
|
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 2 days and 5 hours by commenting |
…and its round 2, and run to main 33c5128 (#883) Round 2b of #883. It answers RV883's full NIT list on ede678c (lane 3, 2026-10-18T09:29:28Z) and RV883's round 2 on 86baec5 (#883 issuecomment-6248362246). Each finding was checked against the code at main 33c5128 before the text changed: - The #843 limit is two: what `adopt-work-dir-shared` leaves open (group write, Windows, root as the other user, this user's own processes, the default work directory), and what it now refuses that v1.1.0 used (another user's directory, a run as root included, a 0777 directory without the sticky bit, a drive mounted without ownership), with the way out. "What a script sees" names `adopt-work-dir-shared` and `adopt-work-dir-occupied`. - The doctor's NEXT lines, not its `members` row, read no url from an unmerged `.gitmodules`; the `make siblings` limit names that row. `siblings.py` has no unmerged rule; the `validate.yml` templates do. - `validate-family.py`'s `family ok` is scoped to `--no-members` and to a holder whose members are checked out. - The wording points: `update-pr-no-repository` under `--pr`, family.py's `git rm -r --cached` line, `MSYSTEM` set and not empty, `..` and `naming-unclassified`, the keys git refuses with no value, the url the root's config keeps, `contracts: cannot be listed`, install.sh's newest tag and redirect location, the scan test's name, `update-rewrite-symlink`, `os.access` for root, `LC_ALL=C`, `update-validators-red`, `heads/main` in both keys, where `plan written to` came, junctions on Python 3.12, the approval click and the run's approvals, and the back-port route's one run. - #457's L1-17 is narrowed by #825; #853 and #854 join #834 as test limits. The refresh past c5359df adds #870 (#864), #876 (#839), #791 (#787), #859 (#838), #780 (#776), #873 (#857) and #875 (#874): 57 pull requests. It drops the limits for #776, #839 and #874's ref rule, rewrites the #838 clauses, and adds the limits those squashes name (#863, #879, #884, an older git without `GIT_NO_LAZY_FETCH`, family.py's link guard). The open list is the 14 that the 09:42Z milestone read leaves after #875: #675, #707, #739, #755, #764, #834, #840, #853, #854, #856, #863, #878, #879 and #884. The six-and-six shape-copy lists stand: from c5359df to 33c5128 the templates and repo_shape.py changed only in the family root's bootstrap.py and validate-family.py, both listed. No other file changes. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-2 (openRepoShape-2)
Claim: #874 (comment)
A v1.1.1 DRAFT. Branch
fix/874-ref-parts, from origin/maind6071c7d.Stacked on
$OPENREPOSHAPE_BIN_DIRthe bytes refuse is read again in the locale's own charset, so a GB18030, GBK or Shift_JIS name that holds no control character and nothing drawn as another is placed, and where nothing can read it the bytes decide as before #867 (install.sh and openRepoShape: the control-character check matches bytes, so a GBK or GB18030 name holding0xC2and a byte from0x80to0x9Fis refused as a control, and a lone0x9B, which an 8-bit terminal acts on, is placed #858), lane 2's: head8bb85cbf(fix/858-bin-dir-bytes), merged as89be75bf. It editsinstall.shandopenRepoShape(the bin directory's byte check).$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFare checked once, before--versionanswers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent #872 (openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866), lane 2's: heada2af74a0(fix/866-launcher-repo-ref), merged as145a5ee3. It editsopenRepoShape(refuse_bad_values,ref_character_fault;ref_faultkeeps the length and shape checks where a ref is used).Both merges were clean, and both heads were unmoved when the branch was pushed. Review the commits after
a1fa6768, the last merge: one, the fix round's8d6c5bdb, on rv874's review (see the last section).456589b3(the change and its tests) and17185b67(the tests only, putting the network stubs under two of the table tests) are what rv874 reviewed ata1fa6768.#867 has landed as
5c54af2. The branch then merged #867's final head0ccda48b(8d9cd4d9), and main's squash5c54af2with-s ours(a1fa6768), because the squash's treea4b0a79dis the final head's. So the diff from main is #872's and this PR's alone:git diff 5c54af2 a1fa6768equalsgit diff 89be75bf 17185b67byte for byte, apart from theindexlines. #872 is still open; when it lands, this branch takes its final head and its squash the same way.#872 has landed as
c5d0bde. The branch merged #872's final heada1cad963(5481abe7), and main's squashc5d0bdewith-s ours(34d2645f), because the squash's tree4156a29ais the final head's. Both parents have now landed, so the diff from main is this PR's alone. The head's tree07b98c23is mainc5d0bdewith the writer's two rounds applied (145a5ee3..17185b67anda1fa6768..8d6c5bdb), measured with a scratch index.This PR lands after #873 (lane 5's,
fix/857-setup-dry-run-offers, which givessetup-project.pygit's per-part rule aswhy_not_a_ref) and does not merge it: #873 editssetup-project.pyonly. #873's head is nowac570069, not the79a33f11the issue names: one commit on top, test-only, andsetup-project.pyis the same bytes.setup.sh's comment nameswhy_not_a_ref, which exists once #873 lands.Main
5c54af2is merged, as above.Decisions
What each copy gates, measured on the stacked base with stub
ghandcurlthat log and fail, a local repository, andGH_TOKEN=bogus. Ona.lock/b,a/.b,a//b,a/b/anda.:setup.shprinted(0) self-bootstrap, rangit clone, and endederror: pathspec '<ref>' did not match any file(s) known to git, exit 1. Its check runs only on the self-bootstrap path (is_shape_checkoutexecssetup-project.pybefore it), so it gates the clone.install.shasked curl forhttps://raw.githubusercontent.com/opensoft/openRepoShape/<ref>/openRepoShape(one request). The version is the URL's path, so the check gates the fetch.openRepoShape --installwithOPENREPOSHAPE_RECORD_REF=1exited 0 and wrote the ref toopenRepoShape.ref.ref_fault's shape line gates the record an install writes and every run reads (read_recorded_refrefuses a bad one before any request), and the doctor'sgit checkout.Every copy holds the rule itself. None of the three only gates a later
setup-project.pyrun: between each check and that run there is a clone, a fetch or a record, so the issue's second option ("say so and leave it to that run") fits none.The rule, one pattern list in all three, plain
casepatterns that bash 3.2 reads, inside each copy's character set, which is neither widened nor narrowed:The first, second, fourth and last are today's. New:
*/.*(a part after a/starts with.),*.lock/*(a part before a/ends in.lock),*//*(an empty part),*/(a/at the end),*.(the name ends in.). That iswhy_not_a_ref's five facts and no more. A leading/and a leading.stay the first pattern's. The line is written out in each file, not shared:install.shandsetup.sharrive alone down a pipe.Checked against git, not argued. Every name of up to five parts from
a,0,.,/,-and.lockthat the character set takes is 3110 names. Each was fed to the pattern list, read out of each of the three files, and togit check-ref-format --branch(2.43.0). The old line disagrees with git on 1122 of them (setup-project.py: a passed-through--dry-runmakes none of the preflight's offers and--shape-refis held to git's rule part by part, so a rehearsal no longer writes git's global config anda.lock/bis refused before the clone (#857) #873's number for the Python rule) and the new one on none, in each file.The words: one sentence per refusal, the same in all three, which states the whole rule. The lead is as it was; the first half says
an ASCII letter or digitandASCII letters, as setup-project.py: a passed-through--dry-runmakes none of the preflight's offers and--shape-refis held to git's rule part by part, so a rehearsal no longer writes git's global config anda.lock/bis refused before the clone (#857) #873'sREF_REwords do (fix round 1, NIT 7); the end,has no '..' and does not end in '.lock'., becomes the rule insetup-project.py's phrases where they fit (an empty part between slashes,the name git gives the lock file it writes beside a ref,a revision range). A sentence per failed part, aswhy_not_a_refhas, was weighed and left: three moredielines in each file, and nothing asks the shell copies to say which part failed.setup.sh's comment said it mirrorsREF_RE; it now says it mirrorschecked_ref, which asksREF_REand thenwhy_not_a_ref, lists the rule, and says the check gates the clone.No test pinned the old words (
git grep -n "does not end in\|starts with a letter or digit" -- testsfound none), so no existing test was changed;REF_RULE_WORDSpins the new ones, in all three.What changed
The refusal of each copy for
a.lock/b, measured by a probe. Before (thetopic.lockrefusal, since the old line passeda.lock/b):After,
setup.sh,install.shandopenRepoShape, fora.lock/band fortopic.lockalike:What each does with the five names, before and after (exit codes;
git clonelines counted on stdout; requests counted in the stub's log):setup.sh --shape-ref <ref>(0) self-bootstrap, 1git clone, exit 1 (pathspec ... did not match)git clonelines, nothing in$TMPDIRinstall.sh <ref>could not fetch)openRepoShape --install(opt-in)<ref>written toopenRepoShape.reftopic.lockwas refused by all three before and is after, andmainis taken by all three.Tests
One table,
REF_PARTSintests/conftest.py, read by three files (so Sonar's duplication gate sees one copy): 36 names, with whether git takes each.a.lock/b,x/y.lock/z,release.lock/v1,a/.b,a/.b/c,a//b,a/b/,a.lock/,a., andtopic.locked,v1.0.0.a/b/.c/d,x/.y/z/w.a/b.,a/..topic.lock,a..b,a/..,a/b.lock,.lock,/a.main,v1.1.0,feature/x-y_z, a 40-hex sha, workBenches'pinned-by-workBenches-no-fetch, anda.lockb,a.lock-x/b,a/b.lockb/c,a./b,a/lock/b,refs/tags/v1.0,v1.0.0-rc.1, and, with a part that starts upper-case,_or-,Feature/ABC-123,fix/_tmp,fix/-wip(fix round 1).git_takes_refasks the git onPATH(git check-ref-format --branch) and never copies its answer.assert_a_copy_answers_as_git_doesholds git to the table's row first, then the copy to git, so a stale row is named as the row.assert_the_refusal_states_the_ref_rulepins the shared sentence,REF_RULE_WORDS, in all three refusals.tests/test_setup_sh.pytest_the_shim_takes_a_shape_ref_as_git_does_before_any_clonesetup.shoutside any checkout,OPENREPOSHAPE_REPOa path nothing is at: a ref it takes prints(0) self-bootstrapand fails at the clone, offline; a ref it refuses exits 2 with the rule, prints nogit cloneline, leaves$TMPDIRemptytests/test_install_sh.pytest_the_installer_takes_a_version_as_git_does_before_any_requestNet.run(<ref>)and its fakes: a version it takes is asked of curl (a call is logged); one it refuses logs no call, exits 2 with the rule, places nothingtests/test_openreposhape_command.pytest_an_install_records_a_ref_as_git_takes_it_and_refuses_the_restinstall_atwith the opt-in (--versionasks only the characters since #872): a ref taken is recorded, exit 0; one refused exits 2 with the rule and places nothingEvery run keeps the network out of reach, with a
ghand acurlthat log and fail, and local paths only.install.sh's test runs under that file's own fakes and asserts a refused version logs no call. The other two putfake_github(tmp_path, (), log=...)first on$PATH(17185b67): thesetup.shtest asserts the log was never written; the launcher test asserts nocurlran and noghbut thegh auth statusan install that placed the command asks (the machine report), and that a refused install asked for nothing.install_attakes anenv_alsofor it.Runs,
/usr/bin/git2.43.0,/usr/bin/python3, one pytest process at a time, in two batches. Batch 1:test_setup_sh,test_install_sh,test_install_sh_filter_repo,test_repo_hygiene,test_cli_reference,test_agents_md_codes. Batch 2:test_openreposhape_command,test_openreposhape_package,test_setup_project_py. Those are the issue's four files, every file that names a word changed (git grep -l), and the common three.145a5ee3, before the change (batch 1 withouttest_install_sh_filter_repo)17185b67, after (same batch 1)a1fa6768, the restacked head rv874 reviewed, before the fix round8d6c5bdb, after the fix roundThe 99 more at
17185b67are the three new tests at 33 rows each; the 9 more at8d6c5bdbare the three rows the fix round adds to each. Nothing was red before. The whole suite is the coordinator's gate.On the base: the three new tests against the base copies of the three files (
145a5ee3): 57 failed, 51 passed (the 17 passing names, three times), measured again with the fix round's 36 rows. Per copy, 13 names the old line takes and git refuses fail, and the 6 refused names it already refused fail on the words.Mutants, each applied to the case line, the copy's own table test run, the file put back at once (
git statusclean after). All 33 (11 per copy) are caught:*.lockthe only.lockchecka.lock/b,x/y.lock/z,release.lock/v1,a/.b,a/.b/c,a//b,a/b/,a.lock/,a.,a/b/.c/d,x/.y/z/w,a/b.,a/.*/.*a/.b,a/.b/c,a/b/.c/d,x/.y/z/w*.lock/*a.lock/b,x/y.lock/z,release.lock/v1*//*a//b*/a/b/*.a.,a/b.*.locktopic.lock,a/b.lock*./*(refuses a part that ends in.but not the name)a./bno empty part between slashescut tono empty partan ASCII letter or digitcut back toa letter or digitsetup.sh: add*/[-_]*(refuses a part that starts with-or_)fix/_tmp,fix/-wipinstall.sh: add*/[A-Z]*(refuses a part that starts upper-case)Feature/ABC-123openRepoShape:[!A-Za-z0-9]*becomes[!a-z0-9]*Feature/ABC-123The last three mutants survive the table without the fix round's three rows (deselected: 33 passed in each copy, as rv874 measured) and are killed with them.
git:
/usr/bin/git2.43.0 only. The issue also names 2.47.1: it was not run here, and CI runs a newer git. The tests ask whichever git runs them, so CI holds the copies to that git's answer. No Windows or macOS run: CI is Linux only (#597).Not changed
misdrawn_characters,locale_reading,refuse_bad_values,checked_repoandref_character_fault, and the length and control-character checks of all three.src/openreposhape/standard.py:215keeps the old rule (not REF_RE.fullmatch(ref) or ".." in ref or ref.endswith(".lock")) and the old sentence.target()checks a ref "before anything reaches git", so it gates the package's clone of the standard. rv874 confirmed it with a probe on a detached copy of the package: 1git cloneinto the cache for each of the five names, thenREFUSED: cloned ... but could not check out. The issue names three bash copies and this PR touches no Python, so it is left; it wants its own issue. Its docstring at:198-201says it is "the launcher'sref_fault, in its order and its words", which is no longer true of the words once this lands.openRepoShape:829shape_checkout(--doctorfrom a download) runsgit clone(:834) beforechecked_ref(:839).test_a_ref_the_doctor_would_check_out_is_validatedpins that order in its docstring ("clones this checkout first, then checks the ref, and refuses beforegit checkout"), the old rule had it fora..btoo, and this PR changes the rule and its words only. Movingchecked_refabove the clone is a one-line change for a later issue. rv874 confirmed it with a probe on an installed copy:--doctorwithOPENREPOSHAPE_REFset to each of the five names shows 1git cloneand 0 checkouts, then the new refusal. Both go to Brett Heap as candidates; nothing is filed without his word.$OPENREPOSHAPE_REF, only set for the run and not recorded, is asked its characters before the fetch (refuse_bad_values, openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866, by design). A ref this rule refuses never reachessetup.sh's check: the fetch ofsetup.shat such a ref fails first (2 requests,gh api .../contents/setup.sh?ref=a.lock/band then curl, at the base and at the head alike), and the run endscould not fetch setup.sh, with nothing cloned. That is openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866's design, and this PR does not move it.docs/cli.md's--shape-refhelp, the README, the handbook and the release notes were grepped for it), so none is touched, and no--helptext changed.Fix round 1, on rv874's review (READY, 2 SHOULDs, 6 NITs)
rv874's review of
a1fa6768: #875 issuecomment-6246173536. No MUST. The fix round is8d6c5bdb, on top of the restack; nothing was rebased, amended or force-pushed.REF_PARTShas no part that starts upper-case,_or-, so three over-refusing mutants pass all 33 rowsFeature/ABC-123,fix/_tmpandfix/-wipjoin the "still pass" group (tests/conftest.py). With the three rows deselected the mutants pass 33 rows in each copy; with them:setup.sh+*/[-_]*failsfix/_tmpandfix/-wip;install.sh+*/[A-Z]*failsFeature/ABC-123;openRepoShapewith[!a-z0-9]*failsFeature/ABC-123. All three tables pass: 108 rowssrc/openreposhape/standard.py:198-219keeps the old rule and sentence, and its docstring says it has the launcher's wordsa/b.anda/.sat under "Refused before #874 as well", but the old line took bothsetup.sh's comment:why_not_a_ref, #874reads as #874's, and "one pattern for each" is not exactwhy_not_a_ref, held here since #874)" and "one or two patterns for each"install.sh:161says "before any request", untrue with no version named (releases/latestis asked first)setup.sh's check follows the fetch, which is untrue for a ref the rule refusescould not fetch setup.sh), and that it is #866's designREF_REwords say "ASCII letters"a ref starts with an ASCII letter or digit, uses only ASCII letters, digits, ...insetup.sh,install.sh,openRepoShapeandREF_RULE_WORDS, byte for byte the same (REF_RULE_WORDSis found once in each file). #873's phrase is "an ASCII letter or digit first, then ASCII letters, digits".setup.sh's comment says it too. Mutant: the lead cut back toa letter or digitfails the 19 refused rows in each copy. No other test pinned the wordsopenRepoShape:834clones before:839checked_refRuns after the fix round,
/usr/bin/git2.43.0: batch 1 561 passed, batch 2 735 passed (1296; 1287 before, ata1fa6768). All 33 mutants are caught again (11 per copy, listed in Tests).Closes #874
🤖 Generated with Claude Code
Summary by Sourcery
Reject Git-invalid ref parts consistently across the shell entry points before any clone, fetch, or record operation.
Bug Fixes:
Enhancements:
Tests: