aboutsummaryrefslogtreecommitdiff
path: root/receivepack
diff options
context:
space:
mode:
Diffstat (limited to 'receivepack')
-rw-r--r--receivepack/int_test.go244
-rw-r--r--receivepack/service/ingest_quarantine.go8
-rw-r--r--receivepack/service/service_test.go2
3 files changed, 247 insertions, 7 deletions
diff --git a/receivepack/int_test.go b/receivepack/int_test.go
index ae8b71a7..7c8825ad 100644
--- a/receivepack/int_test.go
+++ b/receivepack/int_test.go
@@ -4,9 +4,12 @@ import (
"context"
"fmt"
"io"
+ "os"
"strings"
"testing"
+ "time"
+ "codeberg.org/lindenii/furgit/format/pktline"
"codeberg.org/lindenii/furgit/format/sideband64k"
"codeberg.org/lindenii/furgit/internal/testgit"
"codeberg.org/lindenii/furgit/objectid"
@@ -14,8 +17,6 @@ import (
receivepackhooks "codeberg.org/lindenii/furgit/receivepack/hooks"
)
-// TODO: actually test with send-pack
-
func TestReceivePackDeleteOnlyAtomicDeleteSucceeds(t *testing.T) {
t.Parallel()
@@ -618,9 +619,20 @@ func TestReceivePackHookProgressUsesSideBand64K(t *testing.T) {
t.Fatalf("ReadFrame(unpack): %v", err)
}
- if frame.Type != sideband64k.FrameData || string(frame.Payload) != "unpack ok\n" {
+ if frame.Type != sideband64k.FrameData {
t.Fatalf("second frame = %#v", 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)
+ }
})
}
@@ -734,6 +746,146 @@ func TestReceivePackReportStatusV2IncludesRefDetails(t *testing.T) {
})
}
+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().ResolveFully("refs/heads/main")
+ if err != nil {
+ t.Fatalf("ResolveFully(main): %v", err)
+ }
+
+ if resolved.ID != commitID {
+ t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, commitID)
+ }
+ })
+}
+
+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().ResolveFully("refs/heads/main")
+ if err != nil {
+ t.Fatalf("ResolveFully(main): %v", err)
+ }
+
+ if resolved.ID != currentID {
+ t.Fatalf("refs/heads/main = %s, want %s", resolved.ID, currentID)
+ }
+ })
+}
+
type bufferWriteFlusher struct {
strings.Builder
}
@@ -745,3 +897,89 @@ func (bufferWriteFlusher) Flush() error {
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
+}
diff --git a/receivepack/service/ingest_quarantine.go b/receivepack/service/ingest_quarantine.go
index bf918c6d..c41f6c86 100644
--- a/receivepack/service/ingest_quarantine.go
+++ b/receivepack/service/ingest_quarantine.go
@@ -52,9 +52,11 @@ func (service *Service) ingestQuarantine(
req.Pack,
quarantinePackRoot,
service.opts.Algorithm,
- true,
- true,
- service.opts.ExistingObjects,
+ ingest.Options{
+ FixThin: true,
+ WriteRev: true,
+ Base: service.opts.ExistingObjects,
+ },
)
_ = quarantinePackRoot.Close()
diff --git a/receivepack/service/service_test.go b/receivepack/service/service_test.go
index a29e71de..bee8d1aa 100644
--- a/receivepack/service/service_test.go
+++ b/receivepack/service/service_test.go
@@ -10,7 +10,7 @@ import (
"codeberg.org/lindenii/furgit/internal/testgit"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objectstore/memory"
- "codeberg.org/lindenii/furgit/receivepack/internal/service"
+ "codeberg.org/lindenii/furgit/receivepack/service"
)
func TestExecutePackExpectedWithoutObjectsRoot(t *testing.T) {