checkpoint: accept case-folded shard directories in ParseRef - #2403
Merged
Merged
Conversation
On a case-insensitive-but-case-preserving filesystem (macOS APFS, Windows
NTFS defaults), git resolves a new checkpoint ref's shard directory against
existing ones case-insensitively. A ULID checkpoint's canonical shard is
uppercase (e.g. "6B"), so if an unrelated legacy hex checkpoint already
shards to the lowercase form ("6b"), the new ref lands inside that same
lowercase directory instead of a distinct one.
ParseRef then rejected such refs outright: it recomputed the ID's shard and
required an exact, case-sensitive match against the ref's actual path
component, so "6B" != "6b" caused a well-formed, intact checkpoint to be
treated as malformed and silently disappear from `entire checkpoint list`/
`explain`.
Fix: compare the shard case-insensitively (strings.EqualFold) instead of
exactly. Adds regression tests reproducing both directions of the collision.
Fixes entireio#2402
Soph
approved these changes
Sep 15, 2026
Soph
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for doing this. I'll merge it and do a follow up PR addressing the other places where this is an issue in a few minutes. Will close your issue after merging that one.
Thanks!
Soph
enabled auto-merge
September 15, 2026 13:46
Soph
added a commit
that referenced
this pull request
Sep 15, 2026
#2403 made ParseRef tolerate a checkpoint ref stored under the other spelling of its shard directory, which fixes `checkpoint list`. Every other consumer goes the other way — RefName(cid) then repo.Reference — and there it was the filesystem masking the collision, not ParseRef: a case-insensitive filesystem resolves the canonical name onto the folded directory, but only while the ref is loose. git pack-refs (git gc --auto runs it) moves the ref into packed-refs under its stored name, where lookup is an exact string match. resolveLocalRef falls back to the one alternate-cased spelling, which is exhaustive rather than a heuristic: each ID format uses one case exclusively, so a shard bucket has exactly two possible names. That fixes Read (and so `checkpoint explain <id>`), refBase, and GetCheckpointAuthor. writeRefName then targets the spelling an existing ref actually uses, so a write extends the checkpoint instead of forking it. A tolerant read on its own is not enough: refBase would find the old ref and hand back its tip while the CAS created a second ref at the canonical name — and the CAS could not succeed there anyway, since git resolves the expected old value under the name being updated. List dedupes the two spellings for repos a pre-fix CLI already forked, preferring the canonical one so the listing agrees with what Read serves. doctor reports both conditions. It offers no rename for a folded ref: on the filesystem that produces it, update-ref to the canonical name and update-ref -d on the folded one hit the same file while the refs are loose, so the pair that looks like a rename is a delete. Refs #2402 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2JP7ZYMCG44RMG2RGZS7304
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ref #2402
On a case-insensitive-but-case-preserving filesystem (macOS APFS, Windows NTFS defaults), a checkpoint ref's shard directory can get case-folded by the filesystem itself — e.g. a ULID checkpoint whose correct shard is
6Blands inside an already-present6bdirectory left by an unrelated legacy hex checkpoint.ParseRefcompared the recomputed shard against the ref's actual path component byte-for-byte, so"6B" != "6b"caused a well-formed, intact checkpoint to be rejected as malformed and silently disappear fromcheckpoint list/explain.This PR makes that comparison case-insensitive (
strings.EqualFold), matching what the filesystem already does, and adds two regression tests reproducing both directions of the collision (ULID→lowercase dir, legacy-hex→uppercase dir).Verified locally:
mise run fmt && mise run lint(0 issues) andmise run test:cipass.