# Cover batched checkpoint retry recovery

`68845c9`→[main](/content/gh/entireio/git-sync/commits/main/index.html)

## Sessions

e4aeef3a16bbView transcript

## Changes

2

- docs
  - Mrewrite-issue-list.md+4/-2

- internal/syncer
  - Mintegration\_test.go+120

```
157 unmodified lines

158
159
160
161
161
162
163
164
165
266 unmodified lines

432
433
434
434
435
436
437
438
439

157 unmodified lines

Current rewrite note:
- Ownership of stream lifecycle is clearer than on `main`, and the rewrite now has direct tests for key pack-stream close behavior on success and error paths.
- Direct strategy-level error-path tests now verify that relay bootstrap and incremental paths close source pack streams when pushes fail.
- This still wants a fuller close-audit around deeper batched failure paths before it should be considered fully done.
- Batched integration coverage now also exercises a failed checkpoint pack push followed by a resume-from-temp-ref retry.
- This still wants a fuller close-audit around lower-level transport interruption paths before it should be considered fully done.

### 6. Protocol v2 tag fetches request `include-tag` without capability gating

266 unmodified lines

- Batched bootstrap resume mismatch and final-tip cutover paths now have direct integration coverage.
- Batched bootstrap reruns now also cover the "target ref already created, temp ref cleanup still pending" recovery path.
- Injected temp-ref delete failure during batched cutover is now covered end-to-end, including successful recovery on retry.
- Some harder injected pack-transfer failure paths still remain.
- Injected checkpoint pack failure after partial batched progress is now covered end-to-end, including successful resume on retry.
- Some harder transport-interruption and malformed mid-stream failure paths still remain.

### 22. No benchmark coverage for the expensive paths
```

Mdocs/rewrite-issue-list.md+4/-2

```
515 unmodified lines

... (truncated for brevity)

```

func TestBootstrap_IntegrationBatchedPackFailureResumesOnRetry(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeLargeCommits(t, sourceRepo, sourceFS, 5, 200_000)

targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }

sourceServer := newSmartHTTPRepoServerV2(t, sourceRepo)
    targetServer := newSmartHTTPRepoServer(t, targetRepo)
    defer sourceServer.Close()
    defer targetServer.Close()

cfg := Config{
        Source:            Endpoint{URL: sourceServer.RepoURL()},
        Target:            Endpoint{URL: targetServer.RepoURL()},
        ProtocolMode:      protocolModeAuto,
        BatchMaxPackBytes: 350_000,
    }

s, err := newSession(context.Background(), cfg, false)
    if err != nil {
        t.Fatalf("new session: %v", err)
    }
    desired, _, err := planner.BuildDesiredRefs(s.sourceRefMap, planConfig(cfg))
    if err != nil {
        t.Fatalf("build desired refs: %v", err)
    }
    ref := desired[plumbing.NewBranchReferenceName(testBranch)]
    checkpoints, err := bstrap.PlanCheckpoints(context.Background(), bstrap.Params{
        SourceConn:    s.sourceConn,
        SourceService: s.sourceService,
        BatchMaxPack:  cfg.BatchMaxPackBytes,
    }, ref)
    if err != nil {
        t.Fatalf("plan checkpoints: %v", err)
    }
    if len(checkpoints) < 2 {
        t.Fatalf("expected multiple checkpoints, got %d", len(checkpoints))
    }

targetRef := plumbing.NewBranchReferenceName(testBranch)
    tempRef := planner.BootstrapTempRef(targetRef)
    packPushes := 0
    targetServer.receivePackHook = func(req *packp.UpdateRequests, hasPack bool) *packp.ReportStatus {
        if !hasPack || len(req.Commands) == 0 || req.Commands[0].Name != tempRef {
            return nil
        }
        packPushes++
        if packPushes != 2 {
            return nil
        }
        report := packp.NewReportStatus()
        report.UnpackStatus = "ok"
        for _, cmd := range req.Commands {
            report.CommandStatuses = append(report.CommandStatuses, &packp.CommandStatus{
                ReferenceName: cmd.Name,
                Status:        "ng simulated checkpoint pack failure",
            })
        }
        return report
    }

if _, err := Bootstrap(context.Background(), cfg); err == nil {
        t.Fatal("expected first bootstrap attempt to fail on checkpoint pack push")
    }

targetTemp, err := targetRepo.Reference(tempRef, true)
    if err != nil {
        t.Fatalf("resolve temp ref after failed checkpoint push: %v", err)
    }
    if targetTemp.Hash() != checkpoints[0] {
        t.Fatalf("expected temp ref at first checkpoint %s after failure, got %s", checkpoints[0], targetTemp.Hash())
    }
    if _, err := targetRepo.Reference(targetRef, true); err == nil {
        t.Fatalf("expected target ref %s to remain absent after failed checkpoint push", targetRef)
    }

result, err := Bootstrap(context.Background(), cfg)
    if err != nil {
        t.Fatalf("bootstrap retry after checkpoint pack failure failed: %v", err)
    }
    if !result.Relay || result.RelayMode != "bootstrap-batch" {
        t.Fatalf("expected batched bootstrap result, got %+v", result)
    }
    if result.BatchCount >= result.PlannedBatchCount {
        t.Fatalf("expected resumed retry to execute fewer batches than planned, got %+v", result)
    }
    targetHead, err := targetRepo.Reference(targetRef, true)
    if err != nil {
        t.Fatalf("resolve target ref after retry: %v", err)
    }
    if targetHead.Hash() != ref.SourceHash {
        t.Fatalf("expected target head %s after retry, got %s", ref.SourceHash, targetHead.Hash())
    }
    if _, err := targetRepo.Reference(tempRef, true); err == nil {
        t.Fatalf("expected temp ref %s to be deleted after retry", tempRef)
    }
}

func TestBootstrap_IntegrationBatchedLightweightTagCreatesWithoutExtraPack(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeLargeCommits(t, sourceRepo, sourceFS, 5, 200_000)
}
