Include completed branch tips as haves when fetching subsequent branches · Entire

Include completed branch tips as haves when fetching subsequent branches

3c20da5main·

Soph·3mo ago·1 file·+16 added/-2 removed

In a multi-branch bootstrap, each branch fetched as if the target was empty — the source had no way to know that previous branches had already pushed shared objects. For repos like linux where master and nocache-cleanup share ~99% of history, this meant re-transferring ~5 GiB of duplicate objects for the second branch.

Fix: maintain a completedRefs map across the branch loop. After each branch finishes, add its tip (and temp ref) to the map. When fetching checkpoints for the next branch, merge completedRefs into the haves passed to FetchPack. The source can then delta-compress against objects the target already has, producing a much smaller pack for branches that share history.

The tag phase already did this (it built tagTargetRefs from the completed batches). The branch-to-branch loop was the gap.

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

Sessions

da38c1b34ae1View transcript

[?
what test coverage do we have now for replicate?Claude Code·Opus 4.6[1m]·1 step](/content/gh/entireio/git-sync/session/7b2777b1-8075-41f4-a62a-cbfc1b76c01b#timeline-da38c1b34ae1/index.html)

Changes

1

205 unmodified lines

206
207
208
209
210
211
212
213
214
215
216
68 unmodified lines

285
286
287
283
288
289
290
291
67 unmodified lines

359
360
361
362
363
364
365
366
226 unmodified lines

593
594
595
596
597
598
599
592
600
601
602
603
604
605
606
607
608
609

205 unmodified lines

// heuristic. If the resulting pack is too large for the target's
    // receive-pack, the push itself fails and resume handles retry.
    fetchLimit := p.MaxPackBytes
    // Track branch tips already pushed to the target so subsequent branches
    // can advertise them as haves. Without this, the second branch in a
    // multi-branch bootstrap re-sends all shared objects (e.g., linux's
    // master and nocache-cleanup share ~99% of history).
    completedRefs := planner.CopyRefHashMap(p.TargetRefs)

for _, batch := range batches {
        result.PlannedBatchCount += len(batch.Checkpoints)
68 unmodified lines

})

}

packReader, err := packReaderForCheckpoint(ctx, p, batch, checkpoint, current, fetchLimit)
    packReader, err := packReaderForCheckpoint(ctx, p, batch, checkpoint, current, completedRefs, fetchLimit)
    if err != nil {
        return result, fmt.Errorf("fetch source batch pack for %s: %w", batch.Plan.TargetRef, err)
    }
67 unmodified lines

if err := p.TargetPusher.PushCommands(ctx, cmds); err != nil {
        return result, fmt.Errorf("delete bootstrap temp ref for %s: %w", batch.Plan.TargetRef, err)
    }
    completedRefs[batch.Plan.TargetRef] = batch.Plan.SourceHash
    completedRefs[batch.TempRef] = batch.Plan.SourceHash
    p.log("bootstrap batch branch finalized", "branch", batch.Plan.TargetRef.String())
    }

226 unmodified lines

batch plannedBatch,
    checkpoint plumbing.Hash,
    current plumbing.Hash,
    completedRefs map[plumbing.ReferenceName]plumbing.Hash,
    batchLimit int64,
) (io.ReadCloser, error) {
    desired := singleGP(batch.Plan.SourceRef, batch.TempRef, checkpoint)
    haves := planner.SingleHaveMap(current)
    // Merge the current checkpoint (within this branch) with any
    // already-completed branch tips so the source can delta-compress
    // against objects the target already has from previous branches.
     haves := planner.CopyRefHashMap(completedRefs)
    if !current.IsZero() {
        haves[batch.TempRef] = current
    }
    packReader, err := p.SourceService.FetchPack(ctx, p.SourceConn, desired, haves)
    if err != nil {
        return nil, err
    }