You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
same_repository folds the case of a filesystem path, so two bare repositories on a case-sensitive host that differ only in case are called one repository #149
What is wrong.same_repository lowercases the WHOLE normalised remote
before comparing, in both copies of the rule: templates/family-root/scripts/siblings.py:301-330 and — since #146 — scripts/shape_materialize.py's mirror of it.
For a HOST that is right: https://GitHub.com/Org/Repo and https://github.com/org/repo are one repository, and GitHub itself treats the
owner and the name case-insensitively.
For a FILESYSTEM PATH on a case-sensitive host it is not. /srv/mirrors/IRRS.git and /srv/mirrors/irrs.git are two different bare
repositories on Linux, and both functions answer that they are the same one. In siblings.py that is a clone make siblings would FETCH INTO after deciding
it is the member; in shape-doctor.py it is a members row that counts the
wrong directory as the working clone. The estates that mount members from bare
repositories on disk — every test fixture in this suite, and any mirror-based
family — are exactly the ones where a remote is a path.
Raised by Copilot on PR #147
(https://github.com/opensoft/openRepoShape/pull/147#discussion_r3996959231)
against the new copy, and declined THERE on purpose: PR #147's whole subject is
that the doctor and the tool must answer this question identically, and it is
bound by a parity test that runs both over one table. Changing one copy alone
would re-create the disagreement the PR removes, and siblings.py is
digest-pinned — an estate re-pin was in flight while #147 was open.
What would fix it. One change, to BOTH copies, in a commit that may move
the pin:
keep folding case for a remote that has a HOST — anything with a <scheme>:// or in git's scp spelling [user@]host:path — since that is
where case-insensitivity is real;
preserve case for a remote that is a plain filesystem path
(remote_is_a_path / base_is_a_path already draws exactly this
distinction, for the separator question);
extend the parity table in tests/test_shape_doctor.py::test_the_doctor_and_siblings_agree_on_what_one_repository_is
with /srv/mirrors/IRRS.git vs /srv/mirrors/irrs.git (NOT the same) and https://GitHub.com/Org/Repo vs https://github.com/org/repo (the same), so
the two copies stay bound;
and note that Windows and macOS filesystems are case-INsensitive by default,
so the honest rule is "a path compares case-sensitively" rather than a
platform probe: a report that changed its answer with the machine it ran on
would be worse than one that is strict everywhere.
Better still, take it at the moment the two copies are consolidated into scripts/repo_shape.py (the long-term home #146's PR body names), so the rule
is written once.
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T18:42Z) CLAIMED — session 9c6378ea, on Brett Heap's word 'do all 4'. Taken exactly where this issue's last paragraph asks for it: at the moment the two copies are consolidated into scripts/repo_shape.py, which is #155, on branch refactor/remote-rule-in-repo-shape (worktree ~/projects/openRepoShape--consolidate-remote-rule on Eagle, from origin/main 5c63116). The rule written once: case folds for a remote that names a HOST — a <scheme>://<host> or git's scp [user@]host:path — and is PRESERVED for a filesystem path, file:///srv/… included, since that scheme leaves an absolute path and no host behind it. The table rows this issue names land with it. The PR closes both issues.
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T19:25Z) PULL REQUEST OPEN: #156 (head 7038429) — this issue's rule is the ONE deliberate behaviour change in that PR, taken at the consolidation as this issue's last paragraph asks.
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T22:40Z) THE RULE THIS ISSUE ASKS FOR, AS IT NOW STANDS on PR #156 (head 0cc60d9). A remote is a PATH — case preserved — when it is an absolute path, a file:// url whose authority is empty, a Windows drive, or a RELATIVE path written ./ or ../; the relative form joined after Copilot and Codex found that ../mirrors/Fam.git was being lowercased, which is this issue's false positive put straight back for a holder cloned from a relative local remote. Everything else names a HOST and folds case: <scheme>://<host>/…, git's scp [user@]host:path, and the bare Org/Repo a family.yaml row is written as — read as a forge name deliberately, since nothing in an unanchored mirrors/Fam.git says whether it is a directory and git requires a ./ of anybody who means one. The trailing owner/repo fallback folds case whenever either side names a host or a forge repository and compares strictly when both sides are paths, so a mirror at /srv/mirrors/InkRouter/IRRS.git is still matched against the row's bare InkRouter/IRRS while /srv/mirrors/IRRS.git and /srv/mirrors/irrs.git stay two repositories. Strict on every platform, as this issue argues.
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T22:42:11Z) MERGED — #156 → 313c57c (squash), on Brett Heap's word 'land 152 and 156 when they report, then re-pin'. the two copies of 'is this the same repository' — siblings.py's and shape_materialize.py's — become ONE definition in the pinned scripts/repo_shape.py; siblings.py keeps only resolve_relative + clone_url, the doctor imports from repo_shape, the parity test is an identity assertion with an expected answer per row; #149's rule lands once: a filesystem path (absolute, file:// with empty authority, Windows drive, ./ or ../) keeps its case, a host or bare Org/Repo folds it; 2 Copilot rounds + Codex P1 taken, #157 filed for a path/path tail question out of scope; 1154 passed / 1 skipped; Sonar 0; PINNED repo_shape.py + siblings.py → estate re-pin follows. Object complete.
Activity
brettheap commentedon Sep 12, 2026
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T18:42Z) CLAIMED — session 9c6378ea, on Brett Heap's word 'do all 4'. Taken exactly where this issue's last paragraph asks for it: at the moment the two copies are consolidated into
scripts/repo_shape.py, which is #155, on branch refactor/remote-rule-in-repo-shape (worktree ~/projects/openRepoShape--consolidate-remote-rule on Eagle, from origin/main 5c63116). The rule written once: case folds for a remote that names a HOST — a<scheme>://<host>or git's scp[user@]host:path— and is PRESERVED for a filesystem path,file:///srv/…included, since that scheme leaves an absolute path and no host behind it. The table rows this issue names land with it. The PR closes both issues.siblings.py and shape-doctor.py answer "is this the same repository" …
brettheap commentedon Sep 12, 2026
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T19:25Z) PULL REQUEST OPEN: #156 (head 7038429) — this issue's rule is the ONE deliberate behaviour change in that PR, taken at the consolidation as this issue's last paragraph asks.
A RELATIVE remote is a filesystem path too, and the trailing `owner/r…
brettheap commentedon Sep 12, 2026
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T22:40Z) THE RULE THIS ISSUE ASKS FOR, AS IT NOW STANDS on PR #156 (head 0cc60d9). A remote is a PATH — case preserved — when it is an absolute path, a
file://url whose authority is empty, a Windows drive, or a RELATIVE path written./or../; the relative form joined after Copilot and Codex found that../mirrors/Fam.gitwas being lowercased, which is this issue's false positive put straight back for a holder cloned from a relative local remote. Everything else names a HOST and folds case:<scheme>://<host>/…, git's scp[user@]host:path, and the bareOrg/Repoa family.yaml row is written as — read as a forge name deliberately, since nothing in an unanchoredmirrors/Fam.gitsays whether it is a directory and git requires a./of anybody who means one. The trailingowner/repofallback folds case whenever either side names a host or a forge repository and compares strictly when both sides are paths, so a mirror at /srv/mirrors/InkRouter/IRRS.git is still matched against the row's bare InkRouter/IRRS while /srv/mirrors/IRRS.git and /srv/mirrors/irrs.git stay two repositories. Strict on every platform, as this issue argues.The two copies of "is this the same repository" become ONE definition…
brettheap commentedon Sep 12, 2026
(lane openreposhape-2 (openRepoShape-2), 2026-09-12T22:42:11Z) MERGED — #156 → 313c57c (squash), on Brett Heap's word 'land 152 and 156 when they report, then re-pin'. the two copies of 'is this the same repository' — siblings.py's and shape_materialize.py's — become ONE definition in the pinned scripts/repo_shape.py; siblings.py keeps only resolve_relative + clone_url, the doctor imports from repo_shape, the parity test is an identity assertion with an expected answer per row; #149's rule lands once: a filesystem path (absolute, file:// with empty authority, Windows drive, ./ or ../) keeps its case, a host or bare Org/Repo folds it; 2 Copilot rounds + Codex P1 taken, #157 filed for a path/path tail question out of scope; 1154 passed / 1 skipped; Sonar 0; PINNED repo_shape.py + siblings.py → estate re-pin follows. Object complete.
same_repository compares two filesystem paths by EXACT equality, so /…
family.py: `add` refuses a case-variant of a member the family alread…
repo_shape.py and validate-repository-naming.py: a chain link, a decl…