Cover batched checkpoint retry recovery · Entire

Cover batched checkpoint retry recovery

68845c9→main

Sessions

e4aeef3a16bbView transcript

Changes

2

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) }