Push git-refs checkpoints fast-forward-only (no force) · Entire
Push git-refs checkpoints fast-forward-only (no force)
There is no server-side ref protection, so force-pushing per-checkpoint refs by default risks a buggy or racing client silently clobbering good remote history. Switch batchForcePushRefs -> batchPushRefs using a plain ref:ref refspec (fast-forward-only): per-checkpoint refs normally advance by fast-forward so the common case still succeeds, while a genuine non-fast-forward divergence (e.g. the same checkpoint written differently elsewhere) is REJECTED rather than overwritten. On rejection the pre-push logs and leaves the refs queued (not overwritten); reconciling a diverged ref is deferred, and a future rewrite path (e.g. OPF) can use --force-with-lease where it must replace a ref.
Sessions
Changes
cmd/entire/cli/strategy
Mmanual_commit_push.go+5/-3
- Mpush_common.go+15/-11
- Mrefs_push_test.go+53/-13
163 unmodified lines
pushCtx, pushSpan := perf.Start(ctx, "push_checkpoint_refs")
pushErr := batchForcePushRefs(pushCtx, ps.pushTarget(), existing)
pushErr := batchPushRefs(pushCtx, ps.pushTarget(), existing)
pushSpan.End()
if pushErr != nil {
// Leave the refs queued; the next pre-push retries. Non-fatal so the
// user's push proceeds.
logging.Warn(ctx, "git-refs pre-push: batch push failed; refs left queued for retry",
// user's push proceeds. The push is fast-forward-only, so this can mean a
// checkpoint ref diverged on the remote (non-fast-forward) — we leave it
// queued rather than force-overwriting it.
logging.Warn(ctx, "git-refs pre-push: checkpoint ref push failed (possible non-fast-forward divergence); refs left queued, not overwritten",
slog.String("error", pushErr.Error()))
return nil
}
func batchForcePushRefs(ctx context.Context, target string, refs []plumbing.ReferenceName) error { ... }
func batchPushRefs(ctx context.Context, target string, refs []plumbing.ReferenceName) error { ... }
func TestBatchForcePushRefs(t *testing.T) { ... }
func TestBatchPushRefs(t *testing.T) { ... }
func TestBatchForcePushRefs_Empty(t *testing.T) { ... }
func TestBatchPushRefs_RejectsNonFastForward(t *testing.T) { ... }