Cover incremental push failure recovery · Entire

Cover incremental push failure recovery

c3089e9→main·

pjbgf·2mo ago·1 file·+93 added/-0 removed

Inject a one-shot ng status from the target's receive-pack so the first incremental relay attempt fails. Verify the run errors, the target ref stays put (receive-pack only commits ok'd commands), and the retry drives the incremental relay to completion.

Assisted-by: Claude Opus 4.7 noreply@anthropic.com Signed-off-by: Paulo Gomes paulo@entire.io

Sessions

e50870738123View transcript

Changes

1

1207 unmodified lines

1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306

1207 unmodified lines
}

// TestRun_IntegrationIncrementalPushFailureRecoversOnRetry covers the // failure-and-retry contract for the incremental relay path. When the // receive-pack rejects the push (here, a one-shot "ng" status), the run // must surface an error and leave the target unchanged — receive-pack // only commits refs that the server itself reports as ok. A retry against // the same source/target must then drive the incremental relay to // completion, leaving the target at the new source head. func TestRun_IntegrationIncrementalPushFailureRecoversOnRetry(t *testing.T) { sourceRepo, sourceFS := newSourceRepo(t) makeCommits(t, sourceRepo, sourceFS, 2) targetRepo, _ := newSourceRepo(t)

sourceServer := newSmartHTTPRepoServer(t, sourceRepo) targetServer := newSmartHTTPRepoServer(t, targetRepo) targetServer.receivePackThinCap = true defer sourceServer.Close() def defer targetServer.Close()

cfg := Config{ Source: Endpoint{URL: sourceServer.RepoURL()}, Target: Endpoint{URL: targetServer.RepoURL()}, }

if _, err := Run(context.Background(), cfg); err != nil { t.Fatalf("seed sync: %v", err) }

branchRef := plumbing.NewBranchReferenceName(testBranch) preRetryHead, err := targetRepo.Reference(branchRef, true) if err != nil { t.Fatalf("target head after seed: %v", err) }

// Advance source so the next sync produces a fast-forward update plan, // the only branch shape that takes the incremental relay path. makeCommits(t, sourceRepo, sourceFS, 1) sourceHead, err := sourceRepo.Reference(branchRef, true) if err != nil { t.Fatalf("source head: %v", err) }

var pushAttempts int targetServer.receivePackHook = func(req *packp.UpdateRequests, _ bool) *packp.ReportStatus { pushAttempts++ if pushAttempts > 1 { 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 incremental push failure", }) } return report }

if _, err := Run(context.Background(), cfg); err == nil { t.Fatal("expected first incremental sync to fail under injected push rejection") }

afterFail, err := targetRepo.Reference(branchRef, true) if err != nil { t.Fatalf("target head after failed sync: %v", err) } if afterFail.Hash() != preRetryHead.Hash() { t.Fatalf("target advanced despite rejected push: pre=%s post=%s", preRetryHead.Hash(), afterFail.Hash()) }

result, err := Run(context.Background(), cfg) if err != nil { t.Fatalf("retry after incremental failure: %v", err) } if !result.Relay || result.RelayMode != relayModeIncremental { t.Fatalf("expected incremental relay on retry, got %+v", result) } if result.Pushed != 1 { t.Fatalf("expected exactly one pushed ref on retry, got %+v", result) } if pushAttempts != 2 { t.Fatalf("expected exactly two receive-pack attempts (fail + retry), got %d", pushAttempts) }

finalHead, err := targetRepo.Reference(branchRef, true) if err != nil { t.Fatalf("target head after retry: %v", err) } if finalHead.Hash() != sourceHead.Hash() { t.Fatalf("target head not at source after retry: target=%s source=%s", finalHead.Hash(), sourceHead.Hash()) } }

func TestRun_IntegrationBranchMappingAndStats(t *testing.T) { sourceRepo, sourceFS := newSourceRepo(t) makeCommits(t, sourceRepo, sourceFS, 3)