/simplify: make non-branch ref push non-force too (one consistent policy) · Entire

/simplify: make non-branch ref push non-force too (one consistent policy)

0d500bc·

Soph·2w ago·2 files·+10 added/-3 removed

Altitude follow-up to the non-force checkpoint push: tryPushRefCommon (the v1-era per-ref pusher behind doPushRef) still force-pushed non-branch refs (+ref:ref), which contradicts the new fast-forward-only policy for per-checkpoint refs and was a latent footgun — a future caller routing a checkpoint ref through doPushRef would silently overwrite a divergence. The non-branch path has no production caller today (the v1 PrePush loop only pushes the v1 branch), so dropping the force is safe and unifies the policy: every checkpoint ref, branch or per-checkpoint, is fast-forward-only with doPushRef's fetch+rebase recovery on divergence.

Also clarify (comment) that explain_export's RefName-error branch is defensive — cid is already validated by NewCheckpointID, so it can't fire — rather than a swallowed live error.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

89a2a991d773View transcript

Changes

2

205 unmodified lines

206
207
208
209
210
211
212
213
214

205 unmodified lines

// then re-list. Falls through to the v1-branch fetch below otherwise.
    if cpCfg, _ := settings.LoadCheckpointsConfig(ctx); checkpoint.PrimaryIsRefs(cpCfg) { //nolint:errcheck // fail-soft: bad config surfaces via Open elsewhere
        if cid, err := id.NewCheckpointID(prefix); err == nil {
            // cid is already validated by NewCheckpointID above, so RefName can't
            // error here; the guard is defensive — fall back to the v1-branch path
            // rather than fetch a malformed ref.
            refName, refErr := checkpoint.RefName(cid)
            if refErr != nil {
                return nil, lookup

Mcmd/entire/cli/explain_export.go+3

298 unmodified lines

299
300
301
302
303
302
303
304
305
306
307
308
309
310
307
311
312
313
314

298 unmodified lines

// tryPushRefCommon attempts to push a ref. No timeout of its own —
// runs under doPushRef's shared budget. Branch refs use a bare branch-name
// refSpec so existing remote-tracking works; non-branch refs use a force
// refspec ("+refs/...:refs/...") with no tracking shadow.
// refSpec so existing remote-tracking works; non-branch refs use an explicit
// "refs/...:refs/..." refSpec with no tracking shadow. Neither forces: a
// non-fast-forward is rejected so doPushRef's fetch+rebase recovery runs (and a
// genuinely diverged ref is never silently overwritten — there is no
// server-side ref protection). This keeps one consistent non-force policy for
// every checkpoint ref, branch or per-checkpoint.
func tryPushRefCommon(ctx context.Context, remoteName string, ref plumbing.ReferenceName) (pushResult, error) {
    refSpec := ref.Short()
    if !ref.IsBranch() {
        refSpec = "" + ref.String() + ":" + ref.String()
        refSpec = ref.String() + ":" + ref.String()
    }

// Span the actual `git push` subprocess: on a slow remote (e.g. a custom

Mcmd/entire/cli/strategy/push_common.go+7/-3