diff options
| author | 2026-03-07 21:15:54 +0800 | |
|---|---|---|
| committer | 2026-03-07 21:16:32 +0800 | |
| commit | b82515530f10dfebbf99dca501890570f3466910 (patch) | |
| tree | 9574dc49fa7239f7f0c131471f4a6708fd7041d5 /receivepack/int_test.go | |
| parent | receivepack: Set permissions properly (diff) | |
| signature | No signature | |
receivepack: Add hooks
Diffstat (limited to 'receivepack/int_test.go')
| -rw-r--r-- | receivepack/int_test.go | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/receivepack/int_test.go b/receivepack/int_test.go index 8f0d02e6..e05a9bda 100644 --- a/receivepack/int_test.go +++ b/receivepack/int_test.go @@ -403,6 +403,148 @@ func TestReceivePackPackCreatePromotesObjectsAndUpdatesRef(t *testing.T) { }) } +func TestReceivePackHookSeesQuarantinedObjectsAndCanRejectBeforePromotion(t *testing.T) { + t.Parallel() + + //nolint:thelper + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { + t.Parallel() + + sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo}) + _, _, commitID := sender.MakeCommit(t, "pushed commit") + + receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) + repo := receiver.OpenRepository(t) + objectsRoot := receiver.OpenObjectsRoot(t) + + packStream := sender.PackObjectsReader(t, []string{commitID.String()}, false) + t.Cleanup(func() { + _ = packStream.Close() + }) + + var ( + input strings.Builder + output bufferWriteFlusher + hookCalled bool + ) + + input.WriteString(pktlineData( + objectid.Zero(algo).String() + " " + commitID.String() + " refs/heads/main\x00report-status-v2 atomic object-format=" + algo.String() + "\n", + )) + input.WriteString("0000") + + err := receivepack.ReceivePack( + context.Background(), + &output, + io.MultiReader(strings.NewReader(input.String()), packStream), + receivepack.Options{ + Algorithm: algo, + Refs: repo.Refs(), + ExistingObjects: repo.Objects(), + ObjectsRoot: objectsRoot, + Hook: func(ctx context.Context, req receivepack.HookRequest) ([]receivepack.UpdateDecision, error) { + hookCalled = true + + if len(req.Updates) != 1 || req.Updates[0].NewID != commitID { + t.Fatalf("unexpected hook updates: %+v", req.Updates) + } + + if _, _, err := req.ExistingObjects.ReadHeader(commitID); err == nil { + t.Fatalf("existing objects unexpectedly contained quarantined commit %s", commitID) + } + + if _, _, err := req.QuarantinedObjects.ReadHeader(commitID); err != nil { + t.Fatalf("quarantined objects missing commit %s: %v", commitID, err) + } + + return []receivepack.UpdateDecision{{ + Accept: false, + Message: "blocked by hook", + }}, nil + }, + }, + ) + if err != nil { + t.Fatalf("ReceivePack: %v", err) + } + + if !hookCalled { + t.Fatal("hook was not called") + } + + got := output.String() + if !strings.Contains(got, "unpack ok\n") || !strings.Contains(got, "ng refs/heads/main blocked by hook\n") { + t.Fatalf("unexpected receive-pack output %q", got) + } + + if _, err := repo.Refs().Resolve("refs/heads/main"); err == nil { + t.Fatal("refs/heads/main exists after hook rejection") + } + + packs := receiver.Run(t, "count-objects", "-v") + if !strings.Contains(packs, "packs: 0") { + t.Fatalf("count-objects output shows unexpected promoted pack: %q", packs) + } + }) +} + +func TestReceivePackHookCanRejectSubsetOfNonAtomicDeleteOnlyPush(t *testing.T) { + t.Parallel() + + //nolint:thelper + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { + t.Parallel() + + testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo}) + _, _, commitID := testRepo.MakeCommit(t, "base") + testRepo.UpdateRef(t, "refs/heads/main", commitID) + testRepo.UpdateRef(t, "refs/heads/topic", commitID) + + repo := testRepo.OpenRepository(t) + + var ( + input strings.Builder + output bufferWriteFlusher + ) + + input.WriteString(pktlineData( + commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status delete-refs object-format=" + algo.String() + "\n", + )) + input.WriteString(pktlineData( + commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/topic\n", + )) + input.WriteString("0000") + + err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{ + Algorithm: algo, + Refs: repo.Refs(), + ExistingObjects: repo.Objects(), + Hook: func(ctx context.Context, req receivepack.HookRequest) ([]receivepack.UpdateDecision, error) { + return []receivepack.UpdateDecision{ + {Accept: false, Message: "leave main alone"}, + {Accept: true}, + }, nil + }, + }) + if err != nil { + t.Fatalf("ReceivePack: %v", err) + } + + got := output.String() + if !strings.Contains(got, "ng refs/heads/main leave main alone\n") || !strings.Contains(got, "ok refs/heads/topic\n") { + t.Fatalf("unexpected receive-pack output %q", got) + } + + if _, err := repo.Refs().Resolve("refs/heads/main"); err != nil { + t.Fatalf("Resolve(main): %v", err) + } + + if _, err := repo.Refs().Resolve("refs/heads/topic"); err == nil { + t.Fatal("refs/heads/topic still exists after successful delete") + } + }) +} + func TestReceivePackReportStatusV2IncludesRefDetails(t *testing.T) { t.Parallel() |
