aboutsummaryrefslogtreecommitdiff
path: root/receivepack/int_test.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-26 09:14:59 +0000
committerGravatar Runxi Yu2026-03-26 09:14:59 +0000
commit3d25bda9d5da6814661828adabe8a09f9d01aefb (patch)
treed034e28079333f85e5d7b96d921282eddd4798d6 /receivepack/int_test.go
parentobject/id: Empty tree (diff)
signatureNo signature
network/receivepack: Rename from receivepack
Diffstat (limited to 'receivepack/int_test.go')
-rw-r--r--receivepack/int_test.go1047
1 files changed, 0 insertions, 1047 deletions
diff --git a/receivepack/int_test.go b/receivepack/int_test.go
deleted file mode 100644
index cf3ee870..00000000
--- a/receivepack/int_test.go
+++ /dev/null
@@ -1,1047 +0,0 @@
-package receivepack_test
-
-import (
- "context"
- "fmt"
- "io"
- "os"
- "strings"
- "testing"
- "time"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/network/protocol/pktline"
- "codeberg.org/lindenii/furgit/network/protocol/sideband64k"
- objectid "codeberg.org/lindenii/furgit/object/id"
- receivepack "codeberg.org/lindenii/furgit/receivepack"
- receivepackhooks "codeberg.org/lindenii/furgit/receivepack/hooks"
-)
-
-func TestReceivePackDeleteOnlyAtomicDeleteSucceeds(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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status atomic delete-refs object-format=" + algo.String() + "\n",
- ))
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- GitProtocol: "",
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "ok refs/heads/main\n") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/main")
- if err == nil {
- t.Fatal("refs/heads/main still exists after delete push")
- }
- })
-}
-
-func TestReceivePackDeleteOnlyNonAtomicAppliesIndependentDeletes(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")
- _, _, staleID := testRepo.MakeCommit(t, "stale")
- 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(
- staleID.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{
- GitProtocol: "",
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "ng refs/heads/main ") || !strings.Contains(got, "ok refs/heads/topic\n") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/main")
- if err != nil {
- t.Fatalf("Resolve(main): %v", err)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/topic")
- if err == nil {
- t.Fatal("refs/heads/topic still exists after successful delete")
- }
- })
-}
-
-func TestReceivePackDeleteOnlyAtomicFailureLeavesAllRefsUntouched(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")
- _, _, staleID := testRepo.MakeCommit(t, "stale")
- 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(
- staleID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status atomic 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{
- GitProtocol: "",
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "ng refs/heads/main ") || !strings.Contains(got, "ng refs/heads/topic ") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/main")
- if err != nil {
- t.Fatalf("Resolve(main): %v", err)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/topic")
- if err != nil {
- t.Fatalf("Resolve(topic): %v", err)
- }
- })
-}
-
-func TestReceivePackAdvertisesResolvedHEAD(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.SymbolicRef(t, "HEAD", "refs/heads/main")
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
-
- want := commitID.String() + " HEAD"
- if !strings.Contains(got, want) {
- t.Fatalf("HEAD advertisement missing %q in %q", want, got)
- }
- })
-}
-
-func TestReceivePackVersion2FallsBackToV0(t *testing.T) {
- t.Parallel()
-
- testReceivePackProtocolFallback(t, "version=2")
-}
-
-func TestReceivePackHighestRequestedVersionFallsBackToV0ForV2(t *testing.T) {
- t.Parallel()
-
- testReceivePackProtocolFallback(t, "version=1:version=2")
-}
-
-func TestReceivePackWithoutReportStatusWritesNoStatusPayload(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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00delete-refs atomic object-format=" + algo.String() + "\n",
- ))
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if strings.Contains(got, "unpack ") || strings.Contains(got, "ng refs/heads/main ") || strings.Contains(got, "ok refs/heads/main\n") {
- t.Fatalf("unexpected status payload %q", got)
- }
- })
-}
-
-func testReceivePackProtocolFallback(t *testing.T, gitProtocol string) {
- t.Helper()
-
- //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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status atomic delete-refs object-format=" + algo.String() + "\n",
- ))
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- GitProtocol: gitProtocol,
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- if strings.HasPrefix(output.String(), pktlineData("version 1\n")) {
- t.Fatalf("receive-pack output started with protocol v1 preface for %q: %q", gitProtocol, output.String())
- }
- })
-}
-
-func TestReceivePackPackRequestWithoutObjectsRootReportsNotConfigured(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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + commitID.String() + " refs/heads/main\x00report-status object-format=" + algo.String() + "\n",
- ))
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "unpack objects root not configured\n") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
- })
-}
-
-func TestReceivePackPackCreatePromotesObjectsAndUpdatesRef(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
- )
-
- 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,
- },
- )
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "unpack ok\n") || !strings.Contains(got, "ok refs/heads/main\n") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
-
- reopened := receiver.OpenRepository(t)
-
- resolved, err := reopened.Refs().ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if resolved.ID != commitID {
- t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, commitID)
- }
-
- if gotType := receiver.Run(t, "cat-file", "-t", commitID.String()); gotType != "commit" {
- t.Fatalf("cat-file -t = %q, want commit", gotType)
- }
-
- packs := receiver.Run(t, "count-objects", "-v")
- if !strings.Contains(packs, "packs: 1") {
- t.Fatalf("count-objects output missing promoted pack: %q", packs)
- }
- })
-}
-
-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)
- }
-
- _, _, err := req.ExistingObjects.ReadHeader(commitID)
- if err == nil {
- t.Fatalf("existing objects unexpectedly contained quarantined commit %s", commitID)
- }
-
- _, _, err = req.QuarantinedObjects.ReadHeader(commitID)
- if 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)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/main")
- if 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)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/main")
- if err != nil {
- t.Fatalf("Resolve(main): %v", err)
- }
-
- _, err = repo.Refs().Resolve("refs/heads/topic")
- if err == nil {
- t.Fatal("refs/heads/topic still exists after successful delete")
- }
- })
-}
-
-func TestReceivePackHookProgressUsesSideBand64K(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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status side-band-64k atomic delete-refs object-format=" + algo.String() + "\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) {
- _, err := io.WriteString(req.IO.Progress, "hook says hello\n")
- if err != nil {
- return nil, err
- }
-
- return []receivepack.UpdateDecision{{Accept: true}}, nil
- },
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- _, sidebandWire, ok := strings.Cut(output.String(), "0000")
- if !ok {
- t.Fatalf("output missing advertisement flush: %q", output.String())
- }
-
- dec := sideband64k.NewDecoder(strings.NewReader(sidebandWire), sideband64k.ReadOptions{})
-
- sawHookProgress := false
-
- var frame sideband64k.Frame
-
- for {
- var err error
-
- frame, err = dec.ReadFrame()
- if err != nil {
- t.Fatalf("ReadFrame: %v", err)
- }
-
- if frame.Type == sideband64k.FrameProgress && string(frame.Payload) == "hook says hello\n" {
- sawHookProgress = true
- }
-
- if frame.Type == sideband64k.FrameData {
- break
- }
- }
-
- if !sawHookProgress {
- t.Fatal("missing hook progress frame")
- }
-
- statusDec := pktline.NewDecoder(strings.NewReader(string(frame.Payload)), pktline.ReadOptions{})
-
- statusFrame, err := statusDec.ReadFrame()
- if err != nil {
- t.Fatalf("ReadFrame(status unpack): %v", err)
- }
-
- if statusFrame.Type != pktline.PacketData || string(statusFrame.Payload) != "unpack ok\n" {
- t.Fatalf("status frame = %#v", statusFrame)
- }
- })
-}
-
-func TestReceivePackPredefinedRejectForcePushHookRejectsNonFastForward(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, Bare: true})
- _, treeID := testRepo.MakeSingleFileTree(t, "base.txt", []byte("base\n"))
- baseID := testRepo.CommitTree(t, treeID, "base")
- currentID := testRepo.CommitTree(t, treeID, "current", baseID)
- forcedID := testRepo.CommitTree(t, treeID, "forced", baseID)
- testRepo.UpdateRef(t, "refs/heads/main", currentID)
-
- repo := testRepo.OpenRepository(t)
- objectsRoot := testRepo.OpenObjectsRoot(t)
- packStream := testRepo.PackObjectsReader(t, []string{forcedID.String(), "^" + currentID.String()}, false)
- t.Cleanup(func() {
- _ = packStream.Close()
- })
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- currentID.String() + " " + forcedID.String() + " refs/heads/main\x00report-status 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: receivepackhooks.RejectForcePush(),
- },
- )
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "ng refs/heads/main non-fast-forward\n") {
- t.Fatalf("unexpected receive-pack output %q", got)
- }
-
- resolved, err := repo.Refs().ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if resolved.ID != currentID {
- t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, currentID)
- }
- })
-}
-
-func TestReceivePackReportStatusV2IncludesRefDetails(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)
-
- repo := testRepo.OpenRepository(t)
-
- var (
- input strings.Builder
- output bufferWriteFlusher
- )
-
- input.WriteString(pktlineData(
- commitID.String() + " " + objectid.Zero(algo).String() + " refs/heads/main\x00report-status-v2 atomic delete-refs object-format=" + algo.String() + "\n",
- ))
- input.WriteString("0000")
-
- err := receivepack.ReceivePack(context.Background(), &output, strings.NewReader(input.String()), receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- })
- if err != nil {
- t.Fatalf("ReceivePack: %v", err)
- }
-
- got := output.String()
- if !strings.Contains(got, "option refname refs/heads/main\n") {
- t.Fatalf("missing option refname in %q", got)
- }
-
- if !strings.Contains(got, "option old-oid "+commitID.String()+"\n") {
- t.Fatalf("missing option old-oid in %q", got)
- }
-
- if !strings.Contains(got, "option new-oid "+objectid.Zero(algo).String()+"\n") {
- t.Fatalf("missing option new-oid in %q", got)
- }
- })
-}
-
-func TestReceivePackGitPushCreatesBranch(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Parallel()
-
- sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- _, _, commitID := sender.MakeCommit(t, "pushed commit")
- sender.UpdateRef(t, "refs/heads/main", commitID)
-
- receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- repo := receiver.OpenRepository(t)
- objectsRoot := receiver.OpenObjectsRoot(t)
-
- stdout, stderr, clientErr, serverErr := runGitPushFD(
- t,
- sender,
- receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- ObjectsRoot: objectsRoot,
- },
- "push", "--porcelain", "fd::3,4/test", "refs/heads/main:refs/heads/main",
- )
- if clientErr != nil {
- t.Fatalf("git push failed: %v\nstdout=%s\nstderr=%s", clientErr, stdout, stderr)
- }
-
- if serverErr != nil {
- t.Fatalf("ReceivePack: %v", serverErr)
- }
-
- resolved, err := receiver.OpenRepository(t).Refs().ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if resolved.ID != commitID {
- t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, commitID)
- }
- })
-}
-
-func TestReceivePackGitPushRefUpdateWithoutNewObjectsSucceeds(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Parallel()
-
- sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- blobID, treeID := sender.MakeSingleFileTree(t, "base.txt", []byte("base\n"))
- commitID := sender.CommitTree(t, treeID, "base")
- sender.UpdateRef(t, "refs/heads/main", commitID)
-
- receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- receiver.HashObject(t, "blob", sender.RunBytes(t, "cat-file", "blob", blobID.String()))
- receiver.HashObject(t, "tree", sender.RunBytes(t, "cat-file", "tree", treeID.String()))
- receiver.HashObject(t, "commit", sender.RunBytes(t, "cat-file", "commit", commitID.String()))
- receiver.UpdateRef(t, "refs/heads/main", commitID)
-
- repo := receiver.OpenRepository(t)
- objectsRoot := receiver.OpenObjectsRoot(t)
-
- stdout, stderr, clientErr, serverErr := runGitPushFD(
- t,
- sender,
- receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- ObjectsRoot: objectsRoot,
- },
- "push", "--porcelain", "fd::3,4/test", "refs/heads/main:refs/heads/topic",
- )
- if clientErr != nil {
- t.Fatalf("git push failed: %v\nstdout=%s\nstderr=%s", clientErr, stdout, stderr)
- }
-
- if serverErr != nil {
- t.Fatalf("ReceivePack: %v", serverErr)
- }
-
- resolved, err := receiver.OpenRepository(t).Refs().ResolveToDetached("refs/heads/topic")
- if err != nil {
- t.Fatalf("ResolveToDetached(topic): %v", err)
- }
-
- if resolved.ID != commitID {
- t.Fatalf("refs/heads/topic = %s, want %s", resolved.ID, commitID)
- }
-
- 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 TestReceivePackGitPushAtomicDelete(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Parallel()
-
- sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- _, _, commitID := receiver.MakeCommit(t, "base")
- receiver.UpdateRef(t, "refs/heads/main", commitID)
-
- repo := receiver.OpenRepository(t)
-
- stdout, stderr, clientErr, serverErr := runGitPushFD(
- t,
- sender,
- receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- },
- "push", "--porcelain", "--atomic", "fd::3,4/test", ":refs/heads/main",
- )
- if clientErr != nil {
- t.Fatalf("git push failed: %v\nstdout=%s\nstderr=%s", clientErr, stdout, stderr)
- }
-
- if serverErr != nil {
- t.Fatalf("ReceivePack: %v", serverErr)
- }
-
- _, err := receiver.OpenRepository(t).Refs().Resolve("refs/heads/main")
- if err == nil {
- t.Fatal("refs/heads/main still exists after delete push")
- }
- })
-}
-
-func TestReceivePackGitPushRejectsForcedUpdateViaHook(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- t.Parallel()
-
- sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo})
- blobID, treeID := sender.MakeSingleFileTree(t, "base.txt", []byte("base\n"))
- baseID := sender.CommitTree(t, treeID, "base")
- currentID := sender.CommitTree(t, treeID, "current", baseID)
- forcedID := sender.CommitTree(t, treeID, "forced", baseID)
- sender.UpdateRef(t, "refs/heads/main", forcedID)
-
- receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- receiver.HashObject(t, "blob", sender.RunBytes(t, "cat-file", "blob", blobID.String()))
- receiver.HashObject(t, "tree", sender.RunBytes(t, "cat-file", "tree", treeID.String()))
- receiver.HashObject(t, "commit", sender.RunBytes(t, "cat-file", "commit", baseID.String()))
- receiver.HashObject(t, "commit", sender.RunBytes(t, "cat-file", "commit", currentID.String()))
- receiver.UpdateRef(t, "refs/heads/main", currentID)
-
- repo := receiver.OpenRepository(t)
- objectsRoot := receiver.OpenObjectsRoot(t)
-
- stdout, stderr, clientErr, serverErr := runGitPushFD(
- t,
- sender,
- receivepack.Options{
- Algorithm: algo,
- Refs: repo.Refs(),
- ExistingObjects: repo.Objects(),
- ObjectsRoot: objectsRoot,
- Hook: receivepackhooks.RejectForcePush(),
- },
- "push", "--porcelain", "--force", "fd::3,4/test", "refs/heads/main:refs/heads/main",
- )
- if clientErr == nil {
- t.Fatalf("git push unexpectedly succeeded\nstdout=%s\nstderr=%s", stdout, stderr)
- }
-
- if serverErr != nil {
- t.Fatalf("ReceivePack: %v", serverErr)
- }
-
- if !strings.Contains(stdout, "non-fast-forward") && !strings.Contains(stderr, "non-fast-forward") {
- t.Fatalf("git push output missing non-fast-forward message\nstdout=%s\nstderr=%s", stdout, stderr)
- }
-
- resolved, err := receiver.OpenRepository(t).Refs().ResolveToDetached("refs/heads/main")
- if err != nil {
- t.Fatalf("ResolveToDetached(main): %v", err)
- }
-
- if resolved.ID != currentID {
- t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, currentID)
- }
- })
-}
-
-type bufferWriteFlusher struct {
- strings.Builder
-}
-
-func (bufferWriteFlusher) Flush() error {
- return nil
-}
-
-func pktlineData(payload string) string {
- return fmt.Sprintf("%04x%s", len(payload)+4, payload)
-}
-
-type fileWriteFlusher struct {
- *os.File
-}
-
-func (fileWriteFlusher) Flush() error {
- return nil
-}
-
-func runGitPushFD(
- tb testing.TB,
- sender *testgit.TestRepo,
- opts receivepack.Options,
- gitArgs ...string,
-) (stdout string, stderr string, clientErr error, serverErr error) {
- tb.Helper()
-
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- serverRead, clientWrite, err := os.Pipe()
- if err != nil {
- tb.Fatalf("os.Pipe(serverRead/clientWrite): %v", err)
- }
-
- clientRead, serverWrite, err := os.Pipe()
- if err != nil {
- tb.Fatalf("os.Pipe(clientRead/serverWrite): %v", err)
- }
-
- tb.Cleanup(func() {
- _ = serverRead.Close()
- _ = clientWrite.Close()
- _ = clientRead.Close()
- _ = serverWrite.Close()
- })
-
- go func() {
- <-ctx.Done()
-
- _ = serverRead.Close()
- _ = clientWrite.Close()
- _ = clientRead.Close()
- _ = serverWrite.Close()
- }()
-
- serverErrCh := make(chan error, 1)
-
- go func() {
- defer func() {
- _ = serverRead.Close()
- _ = serverWrite.Close()
- }()
-
- serverErrCh <- receivepack.ReceivePack(
- ctx,
- fileWriteFlusher{serverWrite},
- serverRead,
- opts,
- )
- }()
-
- stdoutBytes, stderrBytes, clientErr := sender.RunWithExtraFilesEnvContextE(
- tb,
- ctx,
- nil,
- []*os.File{clientRead, clientWrite},
- gitArgs...,
- )
- _ = clientRead.Close()
- _ = clientWrite.Close()
-
- serverErr = <-serverErrCh
-
- if ctx.Err() != nil {
- tb.Fatalf(
- "git push fd:: timed out\nstdout=%s\nstderr=%s\nclientErr=%v\nserverErr=%v",
- stdoutBytes,
- stderrBytes,
- clientErr,
- serverErr,
- )
- }
-
- return string(stdoutBytes), string(stderrBytes), clientErr, serverErr
-}