aboutsummaryrefslogtreecommitdiff
path: root/receivepack
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
parentobject/id: Empty tree (diff)
signatureNo signature
network/receivepack: Rename from receivepack
Diffstat (limited to 'receivepack')
-rw-r--r--receivepack/advertise.go57
-rw-r--r--receivepack/capabilities_defaults.go17
-rw-r--r--receivepack/commands.go19
-rw-r--r--receivepack/doc.go3
-rw-r--r--receivepack/errors.go15
-rw-r--r--receivepack/hook.go93
-rw-r--r--receivepack/hooks/chain.go51
-rw-r--r--receivepack/hooks/doc.go2
-rw-r--r--receivepack/hooks/reject_force_push.go64
-rw-r--r--receivepack/int_test.go1047
-rw-r--r--receivepack/options.go68
-rw-r--r--receivepack/permissions.go27
-rw-r--r--receivepack/receivepack.go147
-rw-r--r--receivepack/results.go26
-rw-r--r--receivepack/service/apply.go134
-rw-r--r--receivepack/service/command.go32
-rw-r--r--receivepack/service/command_result.go13
-rw-r--r--receivepack/service/doc.go6
-rw-r--r--receivepack/service/execute.go123
-rw-r--r--receivepack/service/hook.go45
-rw-r--r--receivepack/service/hook_apply.go44
-rw-r--r--receivepack/service/ingest_quarantine.go144
-rw-r--r--receivepack/service/options.go36
-rw-r--r--receivepack/service/quarantine.go274
-rw-r--r--receivepack/service/quarantine_test.go184
-rw-r--r--receivepack/service/request.go16
-rw-r--r--receivepack/service/result.go14
-rw-r--r--receivepack/service/run_hook.go168
-rw-r--r--receivepack/service/service.go16
-rw-r--r--receivepack/service/service_test.go99
-rw-r--r--receivepack/service/update.go12
-rw-r--r--receivepack/version.go35
32 files changed, 0 insertions, 3031 deletions
diff --git a/receivepack/advertise.go b/receivepack/advertise.go
deleted file mode 100644
index 0fa010bf..00000000
--- a/receivepack/advertise.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package receivepack
-
-import (
- "errors"
-
- common "codeberg.org/lindenii/furgit/network/protocol/v0v1/server"
- "codeberg.org/lindenii/furgit/ref"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func advertisedRefs(opts Options) ([]common.AdvertisedRef, error) {
- listed, err := opts.Refs.List("")
- if err != nil {
- return nil, err
- }
-
- return buildAdvertisedRefs(opts, listed)
-}
-
-func buildAdvertisedRefs(opts Options, listed []ref.Ref) ([]common.AdvertisedRef, error) {
- refs := make([]common.AdvertisedRef, 0, len(listed))
- for _, entry := range listed {
- switch resolved := entry.(type) {
- case ref.Detached:
- advertised := common.AdvertisedRef{
- Name: resolved.Name(),
- ID: resolved.ID,
- }
-
- if resolved.Peeled != nil {
- advertised.Peeled = resolved.Peeled
- }
-
- refs = append(refs, advertised)
- case ref.Symbolic:
- if resolved.Name() != "HEAD" {
- continue
- }
-
- head, err := opts.Refs.ResolveToDetached("HEAD")
- if err != nil {
- if errors.Is(err, refstore.ErrReferenceNotFound) {
- continue
- }
-
- return nil, err
- }
-
- refs = append(refs, common.AdvertisedRef{
- Name: "HEAD",
- ID: head.ID,
- })
- }
- }
-
- return refs, nil
-}
diff --git a/receivepack/capabilities_defaults.go b/receivepack/capabilities_defaults.go
deleted file mode 100644
index 72c36c30..00000000
--- a/receivepack/capabilities_defaults.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package receivepack
-
-import (
- "crypto/rand"
-)
-
-func defaultAgent() string {
- return "furgit"
-}
-
-func defaultSessionID() string {
- return "furgit-" + rand.Text()
-}
-
-func defaultPushCertNonce() string {
- return "furgit-" + rand.Text()
-}
diff --git a/receivepack/commands.go b/receivepack/commands.go
deleted file mode 100644
index 9c38ec73..00000000
--- a/receivepack/commands.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package receivepack
-
-import (
- protoreceive "codeberg.org/lindenii/furgit/network/protocol/v0v1/server/receivepack"
- "codeberg.org/lindenii/furgit/receivepack/service"
-)
-
-func translateCommands(commands []protoreceive.Command) []service.Command {
- out := make([]service.Command, 0, len(commands))
- for _, command := range commands {
- out = append(out, service.Command{
- OldID: command.OldID,
- NewID: command.NewID,
- Name: command.Name,
- })
- }
-
- return out
-}
diff --git a/receivepack/doc.go b/receivepack/doc.go
deleted file mode 100644
index b63f49d5..00000000
--- a/receivepack/doc.go
+++ /dev/null
@@ -1,3 +0,0 @@
-// Package receivepack provides the application-facing server-side push entry
-// point.
-package receivepack
diff --git a/receivepack/errors.go b/receivepack/errors.go
deleted file mode 100644
index 18e7a135..00000000
--- a/receivepack/errors.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package receivepack
-
-import "errors"
-
-var (
- // ErrMissingAlgorithm reports one missing repository hash algorithm.
- ErrMissingAlgorithm = errors.New("receivepack: missing object id algorithm")
- // ErrMissingRefs reports one missing reference store dependency.
- ErrMissingRefs = errors.New("receivepack: missing refs store")
- // ErrMissingObjects reports one missing object store dependency.
- ErrMissingObjects = errors.New("receivepack: missing objects store")
- // ErrUnsupportedProtocol reports one unsupported requested Git protocol
- // version.
- ErrUnsupportedProtocol = errors.New("receivepack: unsupported protocol version")
-)
diff --git a/receivepack/hook.go b/receivepack/hook.go
deleted file mode 100644
index 431fd66f..00000000
--- a/receivepack/hook.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package receivepack
-
-import (
- "context"
- "io"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- objectstorer "codeberg.org/lindenii/furgit/object/storer"
- "codeberg.org/lindenii/furgit/receivepack/service"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-type HookIO struct {
- Progress io.Writer
- Error io.Writer
-}
-
-// RefUpdate is one requested reference update presented to a receive-pack hook.
-type RefUpdate struct {
- Name string
- OldID objectid.ObjectID
- NewID objectid.ObjectID
-}
-
-// UpdateDecision is one hook decision for a requested reference update.
-type UpdateDecision struct {
- Accept bool
- Message string
-}
-
-// HookRequest is the input presented to a receive-pack hook before quarantine
-// promotion and ref updates.
-//
-// Refs, ExistingObjects, and QuarantinedObjects are borrowed and are only
-// valid for the duration of the hook call.
-type HookRequest struct {
- Refs refstore.ReadingStore
- ExistingObjects objectstorer.Store
- QuarantinedObjects objectstorer.Store
- Updates []RefUpdate
- PushOptions []string
- IO HookIO
-}
-
-// Hook decides whether each requested update should proceed.
-//
-// The hook runs after pack ingestion into quarantine and before quarantine
-// promotion or ref updates. The returned decisions must have the same length as
-// HookRequest.Updates. Hook borrows the data and stores in HookRequest only for
-// the duration of the call.
-type Hook func(context.Context, HookRequest) ([]UpdateDecision, error)
-
-func translateHook(hook Hook) service.Hook {
- if hook == nil {
- return nil
- }
-
- return func(ctx context.Context, req service.HookRequest) ([]service.UpdateDecision, error) {
- translatedUpdates := make([]RefUpdate, 0, len(req.Updates))
- for _, update := range req.Updates {
- translatedUpdates = append(translatedUpdates, RefUpdate{
- Name: update.Name,
- OldID: update.OldID,
- NewID: update.NewID,
- })
- }
-
- decisions, err := hook(ctx, HookRequest{
- Refs: req.Refs,
- ExistingObjects: req.ExistingObjects,
- QuarantinedObjects: req.QuarantinedObjects,
- Updates: translatedUpdates,
- PushOptions: append([]string(nil), req.PushOptions...),
- IO: HookIO{
- Progress: req.IO.Progress,
- Error: req.IO.Error,
- },
- })
- if err != nil {
- return nil, err
- }
-
- out := make([]service.UpdateDecision, 0, len(decisions))
- for _, decision := range decisions {
- out = append(out, service.UpdateDecision{
- Accept: decision.Accept,
- Message: decision.Message,
- })
- }
-
- return out, nil
- }
-}
diff --git a/receivepack/hooks/chain.go b/receivepack/hooks/chain.go
deleted file mode 100644
index 4ce65064..00000000
--- a/receivepack/hooks/chain.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package hooks
-
-import (
- "context"
- "fmt"
-
- receivepack "codeberg.org/lindenii/furgit/receivepack"
-)
-
-// Chain combines hooks by running them in order and intersecting their
-// decisions. The first rejecting message for each update is preserved.
-func Chain(hooks ...receivepack.Hook) receivepack.Hook {
- return func(
- ctx context.Context,
- req receivepack.HookRequest,
- ) ([]receivepack.UpdateDecision, error) {
- decisions := make([]receivepack.UpdateDecision, len(req.Updates))
- for i := range decisions {
- decisions[i].Accept = true
- }
-
- for _, hook := range hooks {
- if hook == nil {
- continue
- }
-
- hookDecisions, err := hook(ctx, req)
- if err != nil {
- return nil, err
- }
-
- if len(hookDecisions) != len(req.Updates) {
- return nil, fmt.Errorf("hook returned %d decisions for %d updates", len(hookDecisions), len(req.Updates))
- }
-
- for i, decision := range hookDecisions {
- if decision.Accept {
- continue
- }
-
- if decisions[i].Accept {
- decisions[i].Message = decision.Message
- }
-
- decisions[i].Accept = false
- }
- }
-
- return decisions, nil
- }
-}
diff --git a/receivepack/hooks/doc.go b/receivepack/hooks/doc.go
deleted file mode 100644
index bef2baf9..00000000
--- a/receivepack/hooks/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package hooks provides a few pre-defined hooks that callers might find useful.
-package hooks
diff --git a/receivepack/hooks/reject_force_push.go b/receivepack/hooks/reject_force_push.go
deleted file mode 100644
index e6b112ea..00000000
--- a/receivepack/hooks/reject_force_push.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package hooks
-
-import (
- "context"
- "errors"
- "fmt"
-
- "codeberg.org/lindenii/furgit/commitquery"
- objectid "codeberg.org/lindenii/furgit/object/id"
- objectmix "codeberg.org/lindenii/furgit/object/storer/mix"
- receivepack "codeberg.org/lindenii/furgit/receivepack"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// RejectForcePush rejects updates whose new value is not a fast-forward of the
-// currently resolved reference.
-func RejectForcePush() receivepack.Hook {
- return func(
- ctx context.Context,
- req receivepack.HookRequest,
- ) ([]receivepack.UpdateDecision, error) {
- _ = ctx
-
- objects := objectmix.New(req.QuarantinedObjects, req.ExistingObjects)
-
- decisions := make([]receivepack.UpdateDecision, len(req.Updates))
- for i := range decisions {
- decisions[i].Accept = true
- }
-
- for i, update := range req.Updates {
- if update.OldID == objectid.Zero(update.OldID.Algorithm()) || update.NewID == objectid.Zero(update.NewID.Algorithm()) {
- continue
- }
-
- current, err := req.Refs.ResolveToDetached(update.Name)
- switch {
- case err == nil:
- case errors.Is(err, refstore.ErrReferenceNotFound):
- continue
- default:
- return nil, fmt.Errorf("resolve %s: %w", update.Name, err)
- }
-
- if current.ID == update.NewID {
- continue
- }
-
- ok, err := commitquery.New(objects, nil).IsAncestor(current.ID, update.NewID)
- if err != nil {
- return nil, fmt.Errorf("check fast-forward %s: %w", update.Name, err)
- }
-
- if !ok {
- decisions[i] = receivepack.UpdateDecision{
- Accept: false,
- Message: "non-fast-forward",
- }
- }
- }
-
- return decisions, nil
- }
-}
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
-}
diff --git a/receivepack/options.go b/receivepack/options.go
deleted file mode 100644
index 139c3839..00000000
--- a/receivepack/options.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package receivepack
-
-import (
- "os"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- objectstorer "codeberg.org/lindenii/furgit/object/storer"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-// Options configures one receive-pack invocation.
-//
-// ReceivePack borrows all configured dependencies.
-//
-// Refs and ExistingObjects are required and must be non-nil.
-// ObjectsRoot is required if the invocation may need to ingest or promote a
-// pack.
-type Options struct {
- // GitProtocol is the raw Git protocol version string from the transport,
- // such as "version=1".
- GitProtocol string
- // Algorithm is the repository object ID algorithm used by the push session.
- Algorithm objectid.Algorithm
- // Refs is the reference store visible to the push.
- Refs refstore.ReadWriteStore
- // ExistingObjects is the object store visible to the push before any newly
- // uploaded quarantined objects are promoted.
- ExistingObjects objectstorer.Store
- // ObjectsRoot is the permanent object storage root beneath which per-push
- // quarantine directories are derived.
- ObjectsRoot *os.Root
- // PromotedObjectPermissions, when non-nil, is applied to objects and
- // directories moved from quarantine into the permanent object store.
- PromotedObjectPermissions *PromotedObjectPermissions
- // Hook, when non-nil, runs after pack ingestion into quarantine and before
- // quarantine promotion or ref updates. Hook is borrowed for the duration of
- // ReceivePack.
- Hook Hook
- // Agent is the receive-pack agent string advertised via capability.
- //
- // When empty, ReceivePack derives one from build info and falls back to
- // "furgit".
- Agent string
- // SessionID is the advertised receive-pack session-id capability value.
- //
- // When empty, ReceivePack generates one random value per invocation.
- SessionID string
- // PushCertNonce is the advertised push-cert nonce capability value.
- //
- // When empty, ReceivePack generates one random value per invocation.
- PushCertNonce string
-}
-
-func validateOptions(opts Options) error {
- if opts.Algorithm == 0 {
- return ErrMissingAlgorithm
- }
-
- if opts.Refs == nil {
- return ErrMissingRefs
- }
-
- if opts.ExistingObjects == nil {
- return ErrMissingObjects
- }
-
- return nil
-}
diff --git a/receivepack/permissions.go b/receivepack/permissions.go
deleted file mode 100644
index 55eb5390..00000000
--- a/receivepack/permissions.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package receivepack
-
-import (
- "io/fs"
-
- "codeberg.org/lindenii/furgit/receivepack/service"
-)
-
-// PromotedObjectPermissions configures the destination permissions applied to
-// objects and directories promoted out of quarantine.
-type PromotedObjectPermissions struct {
- DirMode fs.FileMode
- FileMode fs.FileMode
-}
-
-func translatePromotedObjectPermissions(
- perms *PromotedObjectPermissions,
-) *service.PromotedObjectPermissions {
- if perms == nil {
- return nil
- }
-
- return &service.PromotedObjectPermissions{
- DirMode: perms.DirMode,
- FileMode: perms.FileMode,
- }
-}
diff --git a/receivepack/receivepack.go b/receivepack/receivepack.go
deleted file mode 100644
index d6969bf3..00000000
--- a/receivepack/receivepack.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package receivepack
-
-import (
- "context"
- "io"
-
- "codeberg.org/lindenii/furgit/network/protocol/pktline"
- common "codeberg.org/lindenii/furgit/network/protocol/v0v1/server"
- protoreceive "codeberg.org/lindenii/furgit/network/protocol/v0v1/server/receivepack"
- "codeberg.org/lindenii/furgit/receivepack/service"
-)
-
-// TODO: Some more designing to do. In particular, we'd like to have access to
-// commit graphs and stored object abstractions and such here, especially because
-// hooks might want to access full repos, but we risk creating
-// circular dependencies if we import repository/ here. Might need an interface-ish
-// design, but that risks being over-complicated.
-// Theoretically we could also just give the hooks an os.Root but that
-// feels a bit ugly.
-
-// ReceivePack serves one receive-pack session over r/w.
-//
-// ReceivePack borrows r, w, and all dependencies reachable through opts for
-// the duration of the call. It does not close any of them.
-func ReceivePack(
- ctx context.Context,
- w pktline.WriteFlusher,
- r io.Reader,
- opts Options,
-) error {
- err := validateOptions(opts)
- if err != nil {
- return err
- }
-
- version := parseVersion(opts.GitProtocol)
-
- base := common.NewSession(r, w, common.Options{
- Version: version,
- Algorithm: opts.Algorithm,
- })
-
- agent := opts.Agent
- if agent == "" {
- agent = defaultAgent()
- }
-
- sessionID := opts.SessionID
- if sessionID == "" {
- sessionID = defaultSessionID()
- }
-
- pushCertNonce := opts.PushCertNonce
- if pushCertNonce == "" {
- pushCertNonce = defaultPushCertNonce()
- }
-
- protoSession := protoreceive.NewSession(base, protoreceive.Capabilities{
- ReportStatus: true,
- ReportStatusV2: true,
- DeleteRefs: true,
- SideBand64K: true,
- Quiet: true,
- Atomic: true,
- OfsDelta: true,
- PushOptions: true,
- PushCertNonce: pushCertNonce,
- SessionID: sessionID,
- ObjectFormat: opts.Algorithm,
- Agent: agent,
- })
-
- refs, err := advertisedRefs(opts)
- if err != nil {
- return err
- }
-
- err = protoSession.AdvertiseRefs(common.Advertisement{Refs: refs})
- if err != nil {
- return err
- }
-
- err = base.FlushIO()
- if err != nil {
- return err
- }
-
- req, err := protoSession.ReadRequest()
- if err != nil {
- return err
- }
-
- progressWriter := protoSession.ProgressWriter()
- progressFlush := base.FlushIO
-
- if req.Capabilities.Quiet {
- progressWriter = io.Discard
- progressFlush = nil
- }
-
- serviceReq := &service.Request{
- Commands: translateCommands(req.Commands),
- PushOptions: append([]string(nil), req.PushOptions...),
- Atomic: req.Capabilities.Atomic,
- DeleteOnly: req.DeleteOnly,
- PackExpected: req.PackExpected,
- Pack: r,
- }
-
- svc := service.New(service.Options{
- Algorithm: opts.Algorithm,
- Refs: opts.Refs,
- ExistingObjects: opts.ExistingObjects,
- ObjectsRoot: opts.ObjectsRoot,
- Progress: progressWriter,
- ProgressFlush: progressFlush,
- PromotedObjectPermissions: translatePromotedObjectPermissions(
- opts.PromotedObjectPermissions,
- ),
- Hook: translateHook(opts.Hook),
- HookIO: service.HookIO{
- Progress: progressWriter,
- Error: protoSession.ErrorWriter(),
- },
- })
-
- result, err := svc.Execute(ctx, serviceReq)
- if err != nil {
- return err
- }
-
- protoResult := translateResult(result)
-
- if req.Capabilities.ReportStatusV2 {
- err = protoSession.WriteReportStatusV2(protoResult)
- if err != nil {
- return err
- }
- } else if req.Capabilities.ReportStatus {
- err = protoSession.WriteReportStatus(protoResult)
- if err != nil {
- return err
- }
- }
-
- return base.FlushIO()
-}
diff --git a/receivepack/results.go b/receivepack/results.go
deleted file mode 100644
index ce31ddcd..00000000
--- a/receivepack/results.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package receivepack
-
-import (
- protoreceive "codeberg.org/lindenii/furgit/network/protocol/v0v1/server/receivepack"
- "codeberg.org/lindenii/furgit/receivepack/service"
-)
-
-func translateResult(result *service.Result) protoreceive.ReportStatusResult {
- out := protoreceive.ReportStatusResult{
- UnpackError: result.UnpackError,
- Commands: make([]protoreceive.CommandResult, 0, len(result.Commands)),
- }
-
- for _, command := range result.Commands {
- out.Commands = append(out.Commands, protoreceive.CommandResult{
- Name: command.Name,
- Error: command.Error,
- RefName: command.RefName,
- OldID: command.OldID,
- NewID: command.NewID,
- ForcedUpdate: command.ForcedUpdate,
- })
- }
-
- return out
-}
diff --git a/receivepack/service/apply.go b/receivepack/service/apply.go
deleted file mode 100644
index 8fa500ca..00000000
--- a/receivepack/service/apply.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package service
-
-import (
- "codeberg.org/lindenii/furgit/internal/utils"
- objectid "codeberg.org/lindenii/furgit/object/id"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-func (service *Service) applyAtomic(result *Result, commands []Command) error {
- total := len(commands)
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: 0/%d\r", total)
-
- tx, err := service.opts.Refs.BeginTransaction()
- if err != nil {
- return err
- }
-
- for i, command := range commands {
- err = queueWriteTransaction(tx, command)
- if err != nil {
- _ = tx.Abort()
-
- fillCommandErrors(result, commands, err.Error())
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: failed at %d/%d.\n", i+1, total)
-
- return nil
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: %d/%d\r", i+1, total)
- }
-
- err = tx.Commit()
- if err != nil {
- fillCommandErrors(result, commands, err.Error())
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: failed at commit.\n")
-
- return nil
- }
-
- result.Applied = true
- for _, command := range commands {
- result.Commands = append(result.Commands, successCommandResult(command))
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: done.\n")
-
- return nil
-}
-
-func (service *Service) applyBatch(result *Result, commands []Command) error {
- total := len(commands)
-
- utils.BestEffortFprintf(service.opts.Progress, "updating refs...\r")
-
- batch, err := service.opts.Refs.BeginBatch()
- if err != nil {
- return err
- }
-
- for _, command := range commands {
- queueWriteBatch(batch, command)
- }
-
- batchResults, err := batch.Apply()
- if err != nil && len(batchResults) == 0 {
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: failed at apply.\n")
-
- return err
- }
-
- appliedAny := false
- failedCount := 0
-
- for i, command := range commands {
- item := successCommandResult(command)
- if i < len(batchResults) && batchResults[i].Error != nil {
- item.Error = batchResults[i].Error.Error()
- failedCount++
- } else {
- appliedAny = true
- }
-
- result.Commands = append(result.Commands, item)
-
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: %d/%d\r", i+1, total)
- }
-
- result.Applied = appliedAny
-
- if failedCount == 0 {
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: done.\n")
- } else {
- utils.BestEffortFprintf(service.opts.Progress, "updating refs: failed (%d/%d).\n", failedCount, total)
- }
-
- return nil
-}
-
-func queueWriteTransaction(tx refstore.Transaction, command Command) error {
- if isDelete(command) {
- return tx.Delete(command.Name, command.OldID)
- }
-
- if command.OldID == objectid.Zero(command.OldID.Algorithm()) {
- return tx.Create(command.Name, command.NewID)
- }
-
- return tx.Update(command.Name, command.NewID, command.OldID)
-}
-
-func queueWriteBatch(batch refstore.Batch, command Command) {
- if isDelete(command) {
- batch.Delete(command.Name, command.OldID)
-
- return
- }
-
- if command.OldID == objectid.Zero(command.OldID.Algorithm()) {
- batch.Create(command.Name, command.NewID)
-
- return
- }
-
- batch.Update(command.Name, command.NewID, command.OldID)
-}
-
-func successCommandResult(command Command) CommandResult {
- return CommandResult{
- Name: command.Name,
- RefName: command.Name,
- OldID: objectIDPointer(command.OldID),
- NewID: objectIDPointer(command.NewID),
- }
-}
diff --git a/receivepack/service/command.go b/receivepack/service/command.go
deleted file mode 100644
index 0fd8961e..00000000
--- a/receivepack/service/command.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package service
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// Command is one protocol-independent requested ref update.
-type Command struct {
- OldID objectid.ObjectID
- NewID objectid.ObjectID
- Name string
-}
-
-func fillCommandErrors(result *Result, commands []Command, errText string) {
- for _, command := range commands {
- result.Commands = append(result.Commands, CommandResult{
- Name: command.Name,
- Error: errText,
- RefName: command.Name,
- OldID: objectIDPointer(command.OldID),
- NewID: objectIDPointer(command.NewID),
- })
- }
-}
-
-func isDelete(command Command) bool {
- return command.NewID == objectid.Zero(command.NewID.Algorithm())
-}
-
-func objectIDPointer(id objectid.ObjectID) *objectid.ObjectID {
- out := id
-
- return &out
-}
diff --git a/receivepack/service/command_result.go b/receivepack/service/command_result.go
deleted file mode 100644
index 37549f08..00000000
--- a/receivepack/service/command_result.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package service
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// CommandResult is one per-command execution result.
-type CommandResult struct {
- Name string
- Error string
- RefName string
- OldID *objectid.ObjectID
- NewID *objectid.ObjectID
- ForcedUpdate bool
-}
diff --git a/receivepack/service/doc.go b/receivepack/service/doc.go
deleted file mode 100644
index 37be23f4..00000000
--- a/receivepack/service/doc.go
+++ /dev/null
@@ -1,6 +0,0 @@
-// Package service implements the protocol-independent receive-pack service.
-//
-// A Service borrows the stores, roots, hooks, and I/O endpoints supplied in
-// Options. Callers retain ownership of those dependencies and must keep them
-// valid for each Execute call that uses them.
-package service
diff --git a/receivepack/service/execute.go b/receivepack/service/execute.go
deleted file mode 100644
index 9f373e0d..00000000
--- a/receivepack/service/execute.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package service
-
-import (
- "context"
- "os"
-
- "codeberg.org/lindenii/furgit/internal/utils"
-)
-
-// Execute validates one receive-pack request, optionally ingests its pack into
-// quarantine, runs the optional hook, and applies allowed ref updates.
-func (service *Service) Execute(ctx context.Context, req *Request) (*Result, error) {
- result := &Result{
- Commands: make([]CommandResult, 0, len(req.Commands)),
- }
-
- var (
- quarantineName string
- quarantineRoot *os.Root
- err error
- )
-
- quarantineName, quarantineRoot, ok := service.ingestQuarantine(result, req.Commands, req)
- if !ok {
- return result, nil
- }
-
- if quarantineRoot != nil {
- defer func() {
- _ = quarantineRoot.Close()
- _ = service.opts.ObjectsRoot.RemoveAll(quarantineName)
- }()
- }
-
- for _, command := range req.Commands {
- result.Planned = append(result.Planned, PlannedUpdate{
- Name: command.Name,
- OldID: command.OldID,
- NewID: command.NewID,
- Delete: isDelete(command),
- })
- }
-
- if len(req.Commands) == 0 {
- return result, nil
- }
-
- allowedCommands, allowedIndices, rejected, ok, errText := service.runHook(
- ctx,
- req,
- req.Commands,
- quarantineName,
- )
- if !ok {
- fillCommandErrors(result, req.Commands, errText)
-
- return result, nil
- }
-
- if req.Atomic && len(rejected) != 0 {
- result.Commands = make([]CommandResult, 0, len(req.Commands))
- for index, command := range req.Commands {
- message := rejected[index]
- if message == "" {
- message = "atomic push rejected by hook"
- }
-
- result.Commands = append(result.Commands, resultForHookRejection(command, message))
- }
-
- return result, nil
- }
-
- if len(allowedCommands) == 0 {
- result.Commands = mergeCommandResults(req.Commands, rejected, nil, nil)
-
- return result, nil
- }
-
- if req.PackExpected && quarantineRoot != nil {
- // Git migrates quarantined objects into permanent storage immediately
- // before starting ref updates.
- utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine...\r")
-
- err = service.promoteQuarantine(quarantineName, quarantineRoot)
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine: failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine: done.\n")
- }
-
- if req.Atomic {
- subresult := &Result{}
-
- err := service.applyAtomic(subresult, allowedCommands)
- if err != nil {
- return result, err
- }
-
- result.Commands = mergeCommandResults(req.Commands, rejected, subresult.Commands, allowedIndices)
- result.Applied = subresult.Applied
-
- return result, nil
- }
-
- subresult := &Result{}
-
- err = service.applyBatch(subresult, allowedCommands)
- if err != nil {
- return result, err
- }
-
- result.Commands = mergeCommandResults(req.Commands, rejected, subresult.Commands, allowedIndices)
- result.Applied = subresult.Applied
-
- return result, nil
-}
diff --git a/receivepack/service/hook.go b/receivepack/service/hook.go
deleted file mode 100644
index 750720dd..00000000
--- a/receivepack/service/hook.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package service
-
-import (
- "context"
- "io"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- objectstorer "codeberg.org/lindenii/furgit/object/storer"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-type HookIO struct {
- Progress io.Writer
- Error io.Writer
-}
-
-type RefUpdate struct {
- Name string
- OldID objectid.ObjectID
- NewID objectid.ObjectID
-}
-
-type UpdateDecision struct {
- Accept bool
- Message string
-}
-
-// HookRequest is the borrowed view passed to one Hook invocation.
-//
-// Refs, ExistingObjects, and QuarantinedObjects are borrowed and are only
-// valid for the duration of the hook call.
-type HookRequest struct {
- Refs refstore.ReadingStore
- ExistingObjects objectstorer.Store
- QuarantinedObjects objectstorer.Store
- Updates []RefUpdate
- PushOptions []string
- IO HookIO
-}
-
-// Hook is an optional per-request validation hook.
-//
-// Hook borrows the data and stores in HookRequest only for the duration of the
-// call.
-type Hook func(context.Context, HookRequest) ([]UpdateDecision, error)
diff --git a/receivepack/service/hook_apply.go b/receivepack/service/hook_apply.go
deleted file mode 100644
index 5bd8f596..00000000
--- a/receivepack/service/hook_apply.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package service
-
-func buildHookUpdates(commands []Command) []RefUpdate {
- updates := make([]RefUpdate, 0, len(commands))
- for _, command := range commands {
- updates = append(updates, RefUpdate{
- Name: command.Name,
- OldID: command.OldID,
- NewID: command.NewID,
- })
- }
-
- return updates
-}
-
-func resultForHookRejection(command Command, message string) CommandResult {
- result := successCommandResult(command)
- result.Error = message
-
- return result
-}
-
-func mergeCommandResults(
- commands []Command,
- rejected map[int]string,
- applied []CommandResult,
- appliedIndices []int,
-) []CommandResult {
- out := make([]CommandResult, len(commands))
-
- for index, message := range rejected {
- out[index] = resultForHookRejection(commands[index], message)
- }
-
- for i, appliedResult := range applied {
- if i >= len(appliedIndices) {
- break
- }
-
- out[appliedIndices[i]] = appliedResult
- }
-
- return out
-}
diff --git a/receivepack/service/ingest_quarantine.go b/receivepack/service/ingest_quarantine.go
deleted file mode 100644
index 8e3e2455..00000000
--- a/receivepack/service/ingest_quarantine.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package service
-
-import (
- "os"
-
- "codeberg.org/lindenii/furgit/internal/utils"
- "codeberg.org/lindenii/furgit/packfile/ingest"
-)
-
-func (service *Service) ingestQuarantine(
- result *Result,
- commands []Command,
- req *Request,
-) (string, *os.Root, bool) {
- if !req.PackExpected {
- return "", nil, true
- }
-
- if req.Pack == nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: missing pack stream.\n")
-
- result.UnpackError = "missing pack stream"
- fillCommandErrors(result, commands, "missing pack stream")
-
- return "", nil, false
- }
-
- if service.opts.ObjectsRoot == nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: objects root not configured.\n")
-
- result.UnpackError = "objects root not configured"
- fillCommandErrors(result, commands, "objects root not configured")
-
- return "", nil, false
- }
-
- var err error
-
- err = service.opts.ExistingObjects.Refresh()
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: refresh existing objects: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- return "", nil, false
- }
-
- pending, err := ingest.Ingest(
- req.Pack,
- service.opts.Algorithm,
- ingest.Options{
- FixThin: true,
- WriteRev: true,
- Base: service.opts.ExistingObjects,
- Progress: service.opts.Progress,
- ProgressFlush: service.opts.ProgressFlush,
- },
- )
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- return "", nil, false
- }
-
- if pending.Header().ObjectCount == 0 {
- discarded, err := pending.Discard()
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- return "", nil, false
- }
-
- result.Ingest = &ingest.Result{
- PackHash: discarded.PackHash,
- ObjectCount: discarded.ObjectCount,
- }
-
- utils.BestEffortFprintf(
- service.opts.Progress,
- "unpacking: done (%d objects, %s).\n",
- discarded.ObjectCount,
- discarded.PackHash,
- )
-
- return "", nil, true
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "creating quarantine...\r")
-
- quarantineName, quarantineRoot, err := service.createQuarantineRoot()
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- return "", nil, false
- }
-
- quarantinePackRoot, err := service.openQuarantinePackRoot(quarantineRoot)
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- _ = quarantineRoot.Close()
- _ = service.opts.ObjectsRoot.RemoveAll(quarantineName)
-
- return "", nil, false
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "creating quarantine: done.\n")
- utils.BestEffortFprintf(service.opts.Progress, "unpacking...\r")
-
- ingested, err := pending.Continue(quarantinePackRoot)
-
- _ = quarantinePackRoot.Close()
-
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "unpack failed: %v.\n", err)
-
- result.UnpackError = err.Error()
- fillCommandErrors(result, commands, err.Error())
-
- _ = quarantineRoot.Close()
- _ = service.opts.ObjectsRoot.RemoveAll(quarantineName)
-
- return "", nil, false
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "unpacking: done (%d objects, %s).\n", ingested.ObjectCount, ingested.PackHash)
-
- result.Ingest = &ingested
-
- return quarantineName, quarantineRoot, true
-}
diff --git a/receivepack/service/options.go b/receivepack/service/options.go
deleted file mode 100644
index 7edf4f06..00000000
--- a/receivepack/service/options.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package service
-
-import (
- "io"
- "io/fs"
- "os"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- objectstorer "codeberg.org/lindenii/furgit/object/storer"
- refstore "codeberg.org/lindenii/furgit/ref/store"
-)
-
-type PromotedObjectPermissions struct {
- DirMode fs.FileMode
- FileMode fs.FileMode
-}
-
-// Options configures one protocol-independent receive-pack service.
-//
-// Service borrows all configured dependencies.
-//
-// Refs and ExistingObjects are required and must be non-nil.
-// ObjectsRoot is required if Execute may need to ingest or promote a pack.
-// Progress, ProgressFlush, Hook, and HookIO are optional; when provided they
-// are also borrowed for the duration of Execute.
-type Options struct {
- Algorithm objectid.Algorithm
- Refs refstore.ReadWriteStore
- ExistingObjects objectstorer.Store
- ObjectsRoot *os.Root
- Progress io.Writer
- ProgressFlush func() error
- PromotedObjectPermissions *PromotedObjectPermissions
- Hook Hook
- HookIO HookIO
-}
diff --git a/receivepack/service/quarantine.go b/receivepack/service/quarantine.go
deleted file mode 100644
index 0bd98aeb..00000000
--- a/receivepack/service/quarantine.go
+++ /dev/null
@@ -1,274 +0,0 @@
-package service
-
-import (
- "bytes"
- "crypto/rand"
- "errors"
- "fmt"
- "io"
- "io/fs"
- "os"
- "path"
- "slices"
-)
-
-// createQuarantineRoot creates one per-push quarantine directory beneath the
-// permanent objects root.
-//
-// It returns both the quarantine directory name relative to ObjectsRoot and an
-// opened root for that directory. Callers use the name for later promotion or
-// removal relative to ObjectsRoot, and use the opened root for capability-based
-// access within the quarantine itself.
-func (service *Service) createQuarantineRoot() (string, *os.Root, error) {
- name := "tmp_objdir-incoming-" + rand.Text()
-
- err := service.opts.ObjectsRoot.Mkdir(name, 0o700)
- if err != nil {
- return "", nil, err
- }
-
- root, err := service.opts.ObjectsRoot.OpenRoot(name)
- if err != nil {
- _ = service.opts.ObjectsRoot.RemoveAll(name)
-
- return "", nil, err
- }
-
- return name, root, nil
-}
-
-func (service *Service) openQuarantinePackRoot(quarantineRoot *os.Root) (*os.Root, error) {
- err := quarantineRoot.Mkdir("pack", 0o755)
- if err != nil && !os.IsExist(err) {
- return nil, err
- }
-
- return quarantineRoot.OpenRoot("pack")
-}
-
-func (service *Service) promoteQuarantine(quarantineName string, quarantineRoot *os.Root) error {
- if quarantineName == "" || quarantineRoot == nil {
- return nil
- }
-
- return service.promoteQuarantineDir(quarantineName, quarantineRoot, ".")
-}
-
-func (service *Service) promoteQuarantineDir(quarantineName string, quarantineRoot *os.Root, rel string) error {
- entries, err := fs.ReadDir(quarantineRoot.FS(), rel)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- slices.SortFunc(entries, func(left, right fs.DirEntry) int {
- return packCopyPriority(left.Name()) - packCopyPriority(right.Name())
- })
-
- for _, entry := range entries {
- childRel := entry.Name()
- if rel != "." {
- childRel = path.Join(rel, entry.Name())
- }
-
- if entry.IsDir() {
- err = service.opts.ObjectsRoot.Mkdir(childRel, 0o755)
- if err != nil && !os.IsExist(err) {
- return err
- }
-
- err = service.applyPromotedDirectoryPermissions(childRel)
- if err != nil {
- return err
- }
-
- err = service.promoteQuarantineDir(quarantineName, quarantineRoot, childRel)
- if err != nil {
- return err
- }
-
- continue
- }
-
- err = finalizeQuarantineFile(
- service.opts.ObjectsRoot,
- path.Join(quarantineName, childRel),
- childRel,
- isLooseObjectShardPath(rel),
- service.opts.PromotedObjectPermissions,
- )
- if err == nil {
- continue
- }
-
- return err
- }
-
- return nil
-}
-
-func packCopyPriority(name string) int {
- if !pathHasPackPrefix(name) {
- return 0
- }
-
- switch {
- case path.Ext(name) == ".keep":
- return 1
- case path.Ext(name) == ".pack":
- return 2
- case path.Ext(name) == ".rev":
- return 3
- case path.Ext(name) == ".idx":
- return 4
- default:
- return 5
- }
-}
-
-func pathHasPackPrefix(name string) bool {
- return len(name) >= 4 && name[:4] == "pack"
-}
-
-func isLooseObjectShardPath(rel string) bool {
- return len(rel) == 2 && isHex(rel[0]) && isHex(rel[1])
-}
-
-func isHex(ch byte) bool {
- return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
-}
-
-func (service *Service) applyPromotedDirectoryPermissions(name string) error {
- if service.opts.PromotedObjectPermissions == nil {
- return nil
- }
-
- return service.opts.ObjectsRoot.Chmod(name, service.opts.PromotedObjectPermissions.DirMode)
-}
-
-func applyPromotedFilePermissions(
- root *os.Root,
- name string,
- perms *PromotedObjectPermissions,
-) error {
- if perms == nil {
- return nil
- }
-
- return root.Chmod(name, perms.FileMode)
-}
-
-func finalizeQuarantineFile(
- root *os.Root,
- src, dst string,
- skipCollisionCheck bool,
- perms *PromotedObjectPermissions,
-) error {
- const maxVanishedRetries = 5
-
- for retries := 0; ; retries++ {
- err := root.Link(src, dst)
- switch {
- case err == nil:
- _ = root.Remove(src)
-
- return applyPromotedFilePermissions(root, dst, perms)
- case !errors.Is(err, fs.ErrExist):
- _, statErr := root.Stat(dst)
- switch {
- case statErr == nil:
- err = fs.ErrExist
- case errors.Is(statErr, fs.ErrNotExist):
- renameErr := root.Rename(src, dst)
- if renameErr == nil {
- return applyPromotedFilePermissions(root, dst, perms)
- }
-
- err = renameErr
- default:
- _ = root.Remove(src)
-
- return statErr
- }
- }
-
- if !errors.Is(err, fs.ErrExist) {
- _ = root.Remove(src)
-
- return fmt.Errorf("promote quarantine %q -> %q: %w", src, dst, err)
- }
-
- if skipCollisionCheck {
- _ = root.Remove(src)
-
- return applyPromotedFilePermissions(root, dst, perms)
- }
-
- equal, vanished, cmpErr := compareRootFiles(root, src, dst)
- if vanished {
- if retries >= maxVanishedRetries {
- return fmt.Errorf("promote quarantine %q -> %q: destination repeatedly vanished", src, dst)
- }
-
- continue
- }
-
- if cmpErr != nil {
- return cmpErr
- }
-
- if !equal {
- return fmt.Errorf("promote quarantine %q -> %q: files differ in contents", src, dst)
- }
-
- _ = root.Remove(src)
-
- return applyPromotedFilePermissions(root, dst, perms)
- }
-}
-
-func compareRootFiles(root *os.Root, left, right string) (equal bool, vanished bool, err error) {
- leftFile, err := root.Open(left)
- if err != nil {
- return false, false, err
- }
-
- defer func() {
- _ = leftFile.Close()
- }()
-
- rightFile, err := root.Open(right)
- if err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- return false, true, nil
- }
-
- return false, false, err
- }
-
- defer func() {
- _ = rightFile.Close()
- }()
-
- var leftBuf, rightBuf [4096]byte
-
- for {
- leftN, leftErr := leftFile.Read(leftBuf[:])
- rightN, rightErr := rightFile.Read(rightBuf[:])
-
- if leftErr != nil && !errors.Is(leftErr, io.EOF) {
- return false, false, leftErr
- }
-
- if rightErr != nil && !errors.Is(rightErr, io.EOF) {
- return false, false, rightErr
- }
-
- if leftN != rightN || !bytes.Equal(leftBuf[:leftN], rightBuf[:rightN]) {
- return false, false, nil
- }
-
- if leftErr != nil || rightErr != nil {
- return true, false, nil
- }
- }
-}
diff --git a/receivepack/service/quarantine_test.go b/receivepack/service/quarantine_test.go
deleted file mode 100644
index 86299be2..00000000
--- a/receivepack/service/quarantine_test.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package service //nolint:testpackage
-
-// because we need access to quarantine internals
-
-import (
- "os"
- "path"
- "testing"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer/memory"
-)
-
-type quarantineFixture struct {
- svc *Service
- objectsRoot *os.Root
- quarantineName string
- quarantineRoot *os.Root
-}
-
-func newQuarantineFixture(tb testing.TB, opts Options) *quarantineFixture {
- tb.Helper()
-
- objectsRoot, err := os.OpenRoot(tb.TempDir())
- if err != nil {
- tb.Fatalf("os.OpenRoot: %v", err)
- }
-
- tb.Cleanup(func() {
- _ = objectsRoot.Close()
- })
-
- opts.Algorithm = objectid.AlgorithmSHA1
- opts.ExistingObjects = memory.New(objectid.AlgorithmSHA1)
- opts.ObjectsRoot = objectsRoot
-
- svc := New(opts)
-
- quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
- if err != nil {
- tb.Fatalf("createQuarantineRoot: %v", err)
- }
-
- tb.Cleanup(func() {
- _ = quarantineRoot.Close()
- _ = objectsRoot.RemoveAll(quarantineName)
- })
-
- return &quarantineFixture{
- svc: svc,
- objectsRoot: objectsRoot,
- quarantineName: quarantineName,
- quarantineRoot: quarantineRoot,
- }
-}
-
-func writeMatchingPromotedFile(
- tb testing.TB,
- quarantineRoot, objectsRoot *os.Root,
- dir, name, payload string,
-) {
- tb.Helper()
-
- err := quarantineRoot.Mkdir(dir, 0o755)
- if err != nil {
- tb.Fatalf("Mkdir(%s): %v", dir, err)
- }
-
- err = objectsRoot.Mkdir(dir, 0o755)
- if err != nil {
- tb.Fatalf("Mkdir(dst %s): %v", dir, err)
- }
-
- rel := path.Join(dir, name)
-
- err = quarantineRoot.WriteFile(rel, []byte(payload), 0o644)
- if err != nil {
- tb.Fatalf("WriteFile(quarantine %s): %v", rel, err)
- }
-
- err = objectsRoot.WriteFile(rel, []byte(payload), 0o644)
- if err != nil {
- tb.Fatalf("WriteFile(permanent %s): %v", rel, err)
- }
-}
-
-func TestPromoteQuarantineAppliesConfiguredPermissions(t *testing.T) {
- t.Parallel()
-
- fx := newQuarantineFixture(t, Options{
- PromotedObjectPermissions: &PromotedObjectPermissions{
- DirMode: 0o751,
- FileMode: 0o640,
- },
- })
-
- err := fx.quarantineRoot.Mkdir("ab", 0o700)
- if err != nil {
- t.Fatalf("Mkdir(ab): %v", err)
- }
-
- err = fx.quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte("payload"), 0o600)
- if err != nil {
- t.Fatalf("WriteFile(quarantine loose): %v", err)
- }
-
- err = fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
- if err != nil {
- t.Fatalf("promoteQuarantine: %v", err)
- }
-
- dirInfo, err := fx.objectsRoot.Stat("ab")
- if err != nil {
- t.Fatalf("Stat(ab): %v", err)
- }
-
- if got := dirInfo.Mode().Perm(); got != 0o751 {
- t.Fatalf("dir mode = %o, want 751", got)
- }
-
- fileInfo, err := fx.objectsRoot.Stat(path.Join("ab", "cdef"))
- if err != nil {
- t.Fatalf("Stat(ab/cdef): %v", err)
- }
-
- if got := fileInfo.Mode().Perm(); got != 0o640 {
- t.Fatalf("file mode = %o, want 640", got)
- }
-}
-
-func TestPromoteQuarantineTreatsExistingLooseObjectAsSuccess(t *testing.T) {
- t.Parallel()
-
- fx := newQuarantineFixture(t, Options{})
- writeMatchingPromotedFile(t, fx.quarantineRoot, fx.objectsRoot, "ab", "cdef", "same object bytes")
-
- err := fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
- if err != nil {
- t.Fatalf("promoteQuarantine: %v", err)
- }
-}
-
-func TestPromoteQuarantineRejectsDifferentExistingPackFile(t *testing.T) {
- t.Parallel()
-
- fx := newQuarantineFixture(t, Options{})
-
- err := fx.quarantineRoot.Mkdir("pack", 0o755)
- if err != nil {
- t.Fatalf("Mkdir(pack): %v", err)
- }
-
- err = fx.objectsRoot.Mkdir("pack", 0o755)
- if err != nil {
- t.Fatalf("Mkdir(dst pack): %v", err)
- }
-
- err = fx.quarantineRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte("new bytes"), 0o644)
- if err != nil {
- t.Fatalf("WriteFile(quarantine pack): %v", err)
- }
-
- err = fx.objectsRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte("old bytes"), 0o644)
- if err != nil {
- t.Fatalf("WriteFile(permanent pack): %v", err)
- }
-
- err = fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
- if err == nil {
- t.Fatal("promoteQuarantine unexpectedly succeeded")
- }
-}
-
-func TestPromoteQuarantineAcceptsMatchingExistingPackFile(t *testing.T) {
- t.Parallel()
-
- fx := newQuarantineFixture(t, Options{})
- writeMatchingPromotedFile(t, fx.quarantineRoot, fx.objectsRoot, "pack", "pack-a.pack", "identical pack bytes")
-
- err := fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
- if err != nil {
- t.Fatalf("promoteQuarantine: %v", err)
- }
-}
diff --git a/receivepack/service/request.go b/receivepack/service/request.go
deleted file mode 100644
index 7f9bcc2f..00000000
--- a/receivepack/service/request.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package service
-
-import "io"
-
-// Request is one protocol-independent receive-pack execution request.
-//
-// If PackExpected is true, Pack must be non-nil and remain valid until
-// Execute finishes consuming it.
-type Request struct {
- Commands []Command
- PushOptions []string
- Atomic bool
- DeleteOnly bool
- PackExpected bool
- Pack io.Reader
-}
diff --git a/receivepack/service/result.go b/receivepack/service/result.go
deleted file mode 100644
index 17fc0b6b..00000000
--- a/receivepack/service/result.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package service
-
-import (
- "codeberg.org/lindenii/furgit/packfile/ingest"
-)
-
-// Result is one receive-pack execution result.
-type Result struct {
- UnpackError string
- Commands []CommandResult
- Ingest *ingest.Result
- Planned []PlannedUpdate
- Applied bool
-}
diff --git a/receivepack/service/run_hook.go b/receivepack/service/run_hook.go
deleted file mode 100644
index 94467078..00000000
--- a/receivepack/service/run_hook.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package service
-
-import (
- "context"
- "os"
-
- "codeberg.org/lindenii/furgit/internal/utils"
- objectstorer "codeberg.org/lindenii/furgit/object/storer"
- "codeberg.org/lindenii/furgit/object/storer/loose"
- objectmix "codeberg.org/lindenii/furgit/object/storer/mix"
- "codeberg.org/lindenii/furgit/object/storer/packed"
-)
-
-func (service *Service) runHook(
- ctx context.Context,
- req *Request,
- commands []Command,
- quarantineName string,
-) (
- allowedCommands []Command,
- allowedIndices []int,
- rejected map[int]string,
- ok bool,
- errText string,
-) {
- allowedCommands = append([]Command(nil), commands...)
-
- allowedIndices = make([]int, 0, len(commands))
- for index := range commands {
- allowedIndices = append(allowedIndices, index)
- }
-
- rejected = make(map[int]string)
- if service.opts.Hook == nil {
- return allowedCommands, allowedIndices, rejected, true, ""
- }
-
- utils.BestEffortFprintf(service.opts.Progress, "running hooks...\r")
-
- quarantinedObjects := service.opts.ExistingObjects
-
- var (
- quarantineObjectsStore objectstorer.Store
- quarantineLooseStore *loose.Store
- quarantinePackedStore *packed.Store
- quarantineLooseRoot *os.Root
- quarantinePackRoot *os.Root
- err error
- )
-
- //nolint:nestif
- if quarantineName != "" {
- quarantineLooseRoot, err = service.opts.ObjectsRoot.OpenRoot(quarantineName)
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: %v.\n", err)
-
- return nil, nil, nil, false, err.Error()
- }
-
- quarantineLooseStore, err = loose.New(quarantineLooseRoot, service.opts.Algorithm)
- if err != nil {
- _ = quarantineLooseRoot.Close()
-
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: %v.\n", err)
-
- return nil, nil, nil, false, err.Error()
- }
-
- quarantineObjectsStore = quarantineLooseStore
- quarantinedObjects = quarantineLooseStore
-
- quarantinePackRoot, err = quarantineLooseRoot.OpenRoot("pack")
- if err == nil {
- var packedErr error
-
- quarantinePackedStore, packedErr = packed.New(quarantinePackRoot, service.opts.Algorithm, packed.Options{})
- if packedErr != nil {
- _ = quarantineLooseStore.Close()
- _ = quarantinePackRoot.Close()
- _ = quarantineLooseRoot.Close()
-
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: %v.\n", packedErr)
-
- return nil, nil, nil, false, packedErr.Error()
- }
-
- quarantineObjectsStore = objectmix.New(quarantineLooseStore, quarantinePackedStore)
- quarantinedObjects = quarantineObjectsStore
- } else if !os.IsNotExist(err) {
- _ = quarantineLooseStore.Close()
- _ = quarantineLooseRoot.Close()
-
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: %v.\n", err)
-
- return nil, nil, nil, false, err.Error()
- }
-
- defer func() {
- if quarantineObjectsStore != nil {
- _ = quarantineObjectsStore.Close()
- }
-
- if quarantinePackedStore != nil {
- _ = quarantinePackedStore.Close()
- }
-
- if quarantineLooseStore != nil {
- _ = quarantineLooseStore.Close()
- }
-
- if quarantinePackRoot != nil {
- _ = quarantinePackRoot.Close()
- }
-
- if quarantineLooseRoot != nil {
- _ = quarantineLooseRoot.Close()
- }
- }()
- }
-
- decisions, err := service.opts.Hook(ctx, HookRequest{
- Refs: service.opts.Refs,
- ExistingObjects: service.opts.ExistingObjects,
- QuarantinedObjects: quarantinedObjects,
- Updates: buildHookUpdates(commands),
- PushOptions: append([]string(nil), req.PushOptions...),
- IO: service.opts.HookIO,
- })
- if err != nil {
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: %v.\n", err)
-
- return nil, nil, nil, false, err.Error()
- }
-
- if len(decisions) != len(commands) {
- utils.BestEffortFprintf(service.opts.Progress, "running hooks: failed: wrong decision count.\n")
-
- return nil, nil, nil, false, "hook returned wrong number of update decisions"
- }
-
- allowedCommands = allowedCommands[:0]
- allowedIndices = allowedIndices[:0]
-
- for index, decision := range decisions {
- if decision.Accept {
- allowedCommands = append(allowedCommands, commands[index])
- allowedIndices = append(allowedIndices, index)
-
- continue
- }
-
- message := decision.Message
- if message == "" {
- message = "rejected by hook"
- }
-
- rejected[index] = message
- }
-
- utils.BestEffortFprintf(
- service.opts.Progress,
- "running hooks: done (%d/%d accepted).\n",
- len(allowedCommands),
- len(commands),
- )
-
- return allowedCommands, allowedIndices, rejected, true, ""
-}
diff --git a/receivepack/service/service.go b/receivepack/service/service.go
deleted file mode 100644
index a57fd354..00000000
--- a/receivepack/service/service.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package service
-
-// Service executes protocol-independent receive-pack requests.
-//
-// Service borrows all dependencies supplied in Options.
-type Service struct {
- opts Options
-}
-
-// New creates one receive-pack service.
-//
-// The returned service borrows opts and does not take ownership of any stores,
-// roots, hooks, or I/O endpoints reachable through it.
-func New(opts Options) *Service {
- return &Service{opts: opts}
-}
diff --git a/receivepack/service/service_test.go b/receivepack/service/service_test.go
deleted file mode 100644
index 74a242f8..00000000
--- a/receivepack/service/service_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package service_test
-
-import (
- "context"
- "io/fs"
- "os"
- "strings"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer/memory"
- "codeberg.org/lindenii/furgit/receivepack/service"
-)
-
-func TestExecutePackExpectedWithoutObjectsRoot(t *testing.T) {
- t.Parallel()
-
- //nolint:thelper
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) {
- t.Parallel()
-
- store := memory.New(algo)
- svc := service.New(service.Options{
- Algorithm: algo,
- ExistingObjects: store,
- })
-
- result, err := svc.Execute(context.Background(), &service.Request{
- Commands: []service.Command{{
- Name: "refs/heads/main",
- OldID: objectid.Zero(algo),
- NewID: objectid.Zero(algo),
- }},
- PackExpected: true,
- Pack: strings.NewReader("not a pack"),
- })
- if err != nil {
- t.Fatalf("Execute: %v", err)
- }
-
- if result.UnpackError != "objects root not configured" {
- t.Fatalf("unexpected unpack error %q", result.UnpackError)
- }
- })
-}
-
-func TestExecuteRemovesDerivedQuarantineAfterIngestFailure(t *testing.T) {
- t.Parallel()
-
- //nolint:thelper
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) {
- t.Parallel()
-
- store := memory.New(algo)
- objectsDir := t.TempDir()
-
- objectsRoot, err := os.OpenRoot(objectsDir)
- if err != nil {
- t.Fatalf("os.OpenRoot: %v", err)
- }
-
- t.Cleanup(func() {
- _ = objectsRoot.Close()
- })
-
- svc := service.New(service.Options{
- Algorithm: algo,
- ExistingObjects: store,
- ObjectsRoot: objectsRoot,
- })
-
- result, err := svc.Execute(context.Background(), &service.Request{
- Commands: []service.Command{{
- Name: "refs/heads/main",
- OldID: objectid.Zero(algo),
- NewID: objectid.Zero(algo),
- }},
- PackExpected: true,
- Pack: strings.NewReader("not a pack"),
- })
- if err != nil {
- t.Fatalf("Execute: %v", err)
- }
-
- if result.UnpackError == "" {
- t.Fatal("Execute returned empty unpack error for invalid pack")
- }
-
- entries, err := fs.ReadDir(objectsRoot.FS(), ".")
- if err != nil {
- t.Fatalf("fs.ReadDir: %v", err)
- }
-
- if len(entries) != 0 {
- t.Fatalf("objects root still has entries after failed ingest: %d", len(entries))
- }
- })
-}
diff --git a/receivepack/service/update.go b/receivepack/service/update.go
deleted file mode 100644
index 043f3d51..00000000
--- a/receivepack/service/update.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package service
-
-import objectid "codeberg.org/lindenii/furgit/object/id"
-
-// PlannedUpdate is one ref update that would be applied once ref writing
-// exists.
-type PlannedUpdate struct {
- Name string
- OldID objectid.ObjectID
- NewID objectid.ObjectID
- Delete bool
-}
diff --git a/receivepack/version.go b/receivepack/version.go
deleted file mode 100644
index 9a4544dc..00000000
--- a/receivepack/version.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package receivepack
-
-import (
- "strings"
-
- common "codeberg.org/lindenii/furgit/network/protocol/v0v1/server"
-)
-
-func parseVersion(gitProtocol string) common.Version {
- if gitProtocol == "" {
- return common.Version0
- }
-
- var highestRequested uint8
-
- for field := range strings.SplitSeq(gitProtocol, ":") {
- switch field {
- case "version=0":
- case "version=1":
- if highestRequested < 1 {
- highestRequested = 1
- }
- case "version=2":
- if highestRequested < 2 {
- highestRequested = 2
- }
- }
- }
-
- if highestRequested == 1 {
- return common.Version1
- }
-
- return common.Version0
-}