git-refs pre-push: show push progress instead of a silent stall · Entire

git-refs pre-push: show push progress instead of a silent stall

a4b3f75→main·

Soph·1w ago·1 file·+14 added/-0 removed

The git-refs pre-push path pushed the queued checkpoint refs via batchPushRefs with no output, so pushing a large backlog (e.g. a freshly migrated repo) looked like git had hung for 30+ seconds while git push reported only "Everything up to date" for the branch.

Mirror the v1-branch path's progress line: print "[entire] Pushing N checkpoint ref(s) to ..." with the shared startProgressDots ticker to stderr (which git surfaces during the pre-push hook), and a " done" / "pushed X of N" suffix. The per-ref fetch+replay recovery path gets its own progress line too.

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

Sessions

f55c74d61bafView transcript

Changes

1

2 unmodified lines
3
4
5
6
7
8
9
181 unmodified lines

191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
1 unmodified line

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
3 unmodified lines

228
229
230
231
232
233
234

2 unmodified lines

import (
    "context"
    "errors"
    "fmt"
    "io"
    "log/slog"
    "os"
181 unmodified lines

pushCtx, pushSpan := perf.Start(ctx, "push_checkpoint_refs")
    defer pushSpan.End()

// Progress: pushing many refs over the network can take tens of seconds, so
    // surface it (matching the v1 path's "[entire] Pushing ..." line) instead of
    // leaving the user's git push apparently hung. Written to stderr, which git
    // shows during the pre-push hook.
    displayTarget := displayPushTarget(ps.pushTarget())
    fmt.Fprintf(os.Stderr, "[entire] Pushing %d checkpoint ref(s) to %s...", len(existing), displayTarget)
    stop := startProgressDots(os.Stderr)

// Fast path: push all refs in one round-trip (fast-forward-only). If every
    // ref was up to date or fast-forwarded, we're done.
    if err := batchPushRefs(pushCtx, ps.pushTarget(), existing); err == nil {
        stop(" done")
        if removeErr := queue.Remove(existing); removeErr != nil {
            logging.Warn(ctx, "git-refs pre-push: clear pushed refs from queue failed",
                slog.String("error", removeErr.Error()))
        }
        cleanupPushedShadowBranches(ctx)
        return nil
    }
    stop("")

// At least one ref was rejected — typically a non-fast-forward divergence
    // (the same checkpoint re-written on another machine). Retry per ref with
    // fetch+replay recovery, and remove from the queue only the refs that land
    // (a genuine cherry-pick conflict leaves that ref queued for a later push,
    // never force-overwriting the remote).
    fmt.Fprintf(os.Stderr, "[entire] Some checkpoint refs diverged; syncing %d ref(s) individually...", len(existing))
    stop = startProgressDots(os.Stderr)
    pushed := make([]plumbing.ReferenceName, 0, len(existing))
    for _, ref := range existing {
        if err := pushCheckpointRefWithRecovery(pushCtx, ps.pushTarget(), ref); err != nil {
        }
        pushed = append(pushed, ref)
    }
    stop(fmt.Sprintf(" pushed %d of %d", len(pushed), len(existing)))
    if err := queue.Remove(pushed); err != nil {
        logging.Warn(ctx, "git-refs pre-push: clear pushed refs from queue failed",
                slog.String("error", err.Error()))
    }

Mcmd/entire/cli/strategy/manual_commit_push.go+14