Push git-refs checkpoints from pre-push via the discovery queue · Entire

Push git-refs checkpoints from pre-push via the discovery queue

174ca58·

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

When the configured primary is git-refs, PrePush drains the push-discovery queue and batch force-pushes those per-checkpoint refs (one git push, +ref:ref force refspecs — independent histories, no fetch+rebase recovery) instead of pushing the v1 branch. Default config (git-branch) is unchanged.

A configured git-branch mirror's v1 ref is not pushed here yet (downgrade-safety mirror push is later), and OPF stays descoped for git-refs.

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

Sessions

7ca5586da108View transcript

Changes

3

42 unmodified lines

43
44
45
46
47
48
49
50
51
52
53
54
55
60 unmodified lines

116
117
118
112
113
114
115
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
3 unmodified lines

195
196
197
125
126
198

42 unmodified lines

return nil
    }

// git-refs primary: push the per-checkpoint refs recorded in the push queue
    // instead of the single v1 branch. (A configured git-branch mirror's v1 ref
    // is not pushed here yet — mirror push for downgrade safety is a later step.)
    if cpCfg, _ := settings.LoadCheckpointsConfig(ctx); checkpoint.PrimaryIsRefs(cpCfg) { //nolint:errcheck // fail-soft: a bad checkpoints block already surfaces via Open; default to no refs push
        return s.prePushCheckpointRefs(ctx, ps)
    }

refs := checkpoint.ResolveRefs(ctx)
    if !syncCheckpointPolicyForPrePush(ctx, ps) {
        return nil
    }
    pushCheckpointsSpan.End()

// Post-push cleanup: only when all configured checkpoint refs were pushed
    // successfully, so we know condensed checkpoint data reached the remote.
    // Failures here are non-fatal — shadow branches just accumulate until
    // `entire clean` or the next successful push.
    cleanupPushedShadowBranches(ctx)
    return nil
}

// prePushCheckpointRefs drains the per-checkpoint push queue and batch force-pushes
// the recorded refs (git-refs primary). Transient push failures are logged and
// swallowed — like the v1 path, they must not block the user's git push — and the
// refs stay queued for the next pre-push. OPF is not applied (it is descoped for
// the git-refs store for now).
func (s *ManualCommitStrategy) prePushCheckpointRefs(ctx context.Context, ps pushSettings) error {
    repo, err := OpenRepository(ctx)
    if err != nil {
        logging.Warn(ctx, "git-refs pre-push: open repo failed; skipping checkpoint push",
            slog.String("error", err.Error()))
        return nil
    }
    defer repo.Close()

queue, err := checkpoint.PushQueueForRepo(ctx, repo)
    if err != nil {
        logging.Warn(ctx, "git-refs pre-push: resolve push queue failed; skipping checkpoint push",
            slog.String("error", err.Error()))
        return nil
    }
    queued, err := queue.Drain()
    if err != nil {
        logging.Warn(ctx, "git-refs pre-push: drain push queue failed; skipping checkpoint push",
            slog.String("error", err.Error()))
        return nil
    }
    if len(queued) == 0 {
        return nil
    }

// Drop stale entries (refs no longer present locally) so they don't block
    // the queue forever, then push what remains.
    existing, stale := partitionLocalRefs(repo, queued)
    if len(stale) > 0 {
        if err := queue.Remove(stale); err != nil {
            logging.Warn(ctx, "git-refs pre-push: prune stale queue entries failed",
                slog.String("error", err.Error()))
        }
    }
    if len(existing) == 0 {
        return nil
    }

pushCtx, pushSpan := perf.Start(ctx, "push_checkpoint_refs")
    pushErr := batchForcePushRefs(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",
            slog.String("error", pushErr.Error()))
        return nil
    }
    if err := queue.Remove(existing); err != nil {
        logging.Warn(ctx, "git-refs pre-push: clear pushed refs from queue failed",
            slog.String("error", err.Error()))
    }

cleanupPushedShadowBranches(ctx)
    return nil
}

// cleanupPushedShadowBranches runs post-push shadow-branch cleanup. Failures are
// non-fatal — shadow branches just accumulate until `entire clean` or the next
// successful push.
func cleanupPushedShadowBranches(ctx context.Context) {
    if deleted, cleanupErr := CleanupPushedShadowBranches(ctx); cleanupErr != nil {
        logging.Warn(ctx, "post-push shadow branch cleanup failed",
            slog.String("error", cleanupErr.Error()),
            slog.Int("count", deleted),
    )
    }

return nil
}
``

Mcmd/entire/cli/strategy/manual_commit_push.go+77/-6

21 unmodified lines

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

21 unmodified lines

"github.com/go-git/go-git/v6/plumbing/object"

// partitionLocalRefs splits refs into those that exist locally (pushable) and // those that don't (stale queue entries — e.g. a checkpoint ref deleted by // cleanup). Stale refs can never push, so callers drop them from the queue // rather than retrying them forever. func partitionLocalRefs(repo *git.Repository, refs []plumbing.ReferenceName) (existing, stale []plumbing.ReferenceName) { for _, ref := range refs { if _, err := repo.Reference(ref, false); err != nil { stale = append(stale, ref) continue } existing = append(existing, ref) } return existing, stale }

// batchForcePushRefs pushes all of refs to target in a single git push. Each // uses a force refspec (+ref:ref), matching how non-branch checkpoint refs are // already pushed: per-checkpoint refs have independent histories with no // remote-tracking shadow, so there is no fast-forward to preserve and no // fetch+rebase recovery to attempt. Batching keeps a backfill of many refs to // one network round-trip. It is all-or-nothing: on error the caller leaves every // ref queued for the next pre-push (partial-failure reconciliation is out of // scope for now). func batchForcePushRefs(ctx context.Context, target string, refs []plumbing.ReferenceName) error { if len(refs) == 0 { return nil } refSpecs := make([]string, 0, len(refs)) for _, ref := range refs { refSpecs = append(refSpecs, "+"+ref.String()+":"+ref.String()) } if _, err := remote.PushWithOptions(ctx, remote.PushOptions{Remote: target, RefSpecs: refSpecs}); err != nil { return fmt.Errorf("batch push %d checkpoint refs: %w", len(refs), err) } return nil }

// pushRefIfNeeded pushes a ref to the given target if it has unpushed changes. // The target can be a remote name (e.g., "origin") or a URL for direct push. // For branch refs, the "has unpushed" optimization consults the remote-tracking