migrate: don't clobber a valid ref on a transient refBase read error · Entire
migrate: don't clobber a valid ref on a transient refBase read error
c1989b5→main·
Soph·1w ago·1 file·+14 added/-5 removed
MigrateBranchToRefs collapsed every refBase error to parent=ZeroHash,
so a momentary read failure — a concurrent repack, lock contention —
made the migration treat a still-valid checkpoint ref as absent, wrap
the branch snapshot in a fresh orphan commit, and setRef over the good
ref (discarding its history; the follow-up fast-forward-only push then
rejects it).
Split the cases: an absent ref stays an orphan, a ref whose commit
object is genuinely missing (ErrObjectNotFound: corrupt/pruned) is still
re-imported as an orphan, but any other read error now aborts the
checkpoint so an idempotent re-run retries rather than rewriting a ref
that was only transiently unreadable.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
01KWY6NRP1P3QH7DA54EYCM61WView transcript
Changes
1
cmd/entire/cli/checkpoint
Mmigrate.go+14/-5
75 unmodified lines
76
77
78
79
80
81
82
79
80
81
82
83
84
85
86
87
84
88
89
90
91
92
93
94
95
96
97
75 unmodified lines
}
// The existing ref drives the idempotency check and the new commit's
// parent. Only a ref that resolves to a real commit becomes the parent —
// unreadable ref state is treated as absent (orphan) rather than
// parenting the new commit on a bad hash, which would corrupt the commit
// graph for fetch+replay.
// parent. refBase separates three cases we must not conflate:
// - no ref yet (nil error, zero hash): a brand-new orphan.
// - ref present but its commit object is missing (corrupt or pruned):
// treat as absent and re-import as an orphan rather than parenting on
// a bad hash, which would corrupt the commit graph for fetch+replay.
// - a genuine read failure (transient IO, a concurrent repack): do NOT
// clobber a possibly-valid ref with an orphan; abort this checkpoint
// so an idempotent re-run can retry once the repo is readable again.
parent, _, err := refsStore.refBase(cid)
if err != nil {
switch {
case err == nil:
// parent is the ref tip, or zero when the ref is absent.
case errors.Is(err, plumbing.ErrObjectNotFound):
parent = plumbing.ZeroHash
default:
return fmt.Errorf("resolve existing ref for checkpoint %s: %w", cid, err)
}
// Skip when this snapshot was already imported anywhere on the ref's
// first-parent chain: the ref may have advanced past it through
Mcmd/entire/cli/checkpoint/migrate.go+14/-5