aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--receivepack/int_test.go30
-rw-r--r--receivepack/internal/service/apply.go1
-rw-r--r--receivepack/internal/service/execute.go140
-rw-r--r--receivepack/internal/service/ingest_quarantine.go75
-rw-r--r--receivepack/internal/service/quarantine.go7
-rw-r--r--receivepack/internal/service/quarantine_test.go225
-rw-r--r--receivepack/internal/service/run_hook.go73
7 files changed, 289 insertions, 262 deletions
diff --git a/receivepack/int_test.go b/receivepack/int_test.go
index e05a9bda..c2d260bb 100644
--- a/receivepack/int_test.go
+++ b/receivepack/int_test.go
@@ -52,7 +52,8 @@ func TestReceivePackDeleteOnlyAtomicDeleteSucceeds(t *testing.T) {
t.Fatalf("unexpected receive-pack output %q", got)
}
- if _, err := repo.Refs().Resolve("refs/heads/main"); err == nil {
+ _, err = repo.Refs().Resolve("refs/heads/main")
+ if err == nil {
t.Fatal("refs/heads/main still exists after delete push")
}
})
@@ -101,11 +102,13 @@ func TestReceivePackDeleteOnlyNonAtomicAppliesIndependentDeletes(t *testing.T) {
t.Fatalf("unexpected receive-pack output %q", got)
}
- if _, err := repo.Refs().Resolve("refs/heads/main"); err != nil {
+ _, err = repo.Refs().Resolve("refs/heads/main")
+ if err != nil {
t.Fatalf("Resolve(main): %v", err)
}
- if _, err := repo.Refs().Resolve("refs/heads/topic"); err == nil {
+ _, err = repo.Refs().Resolve("refs/heads/topic")
+ if err == nil {
t.Fatal("refs/heads/topic still exists after successful delete")
}
})
@@ -154,11 +157,13 @@ func TestReceivePackDeleteOnlyAtomicFailureLeavesAllRefsUntouched(t *testing.T)
t.Fatalf("unexpected receive-pack output %q", got)
}
- if _, err := repo.Refs().Resolve("refs/heads/main"); err != nil {
+ _, err = repo.Refs().Resolve("refs/heads/main")
+ if err != nil {
t.Fatalf("Resolve(main): %v", err)
}
- if _, err := repo.Refs().Resolve("refs/heads/topic"); err != nil {
+ _, err = repo.Refs().Resolve("refs/heads/topic")
+ if err != nil {
t.Fatalf("Resolve(topic): %v", err)
}
})
@@ -449,11 +454,13 @@ func TestReceivePackHookSeesQuarantinedObjectsAndCanRejectBeforePromotion(t *tes
t.Fatalf("unexpected hook updates: %+v", req.Updates)
}
- if _, _, err := req.ExistingObjects.ReadHeader(commitID); err == nil {
+ _, _, err := req.ExistingObjects.ReadHeader(commitID)
+ if err == nil {
t.Fatalf("existing objects unexpectedly contained quarantined commit %s", commitID)
}
- if _, _, err := req.QuarantinedObjects.ReadHeader(commitID); err != nil {
+ _, _, err = req.QuarantinedObjects.ReadHeader(commitID)
+ if err != nil {
t.Fatalf("quarantined objects missing commit %s: %v", commitID, err)
}
@@ -477,7 +484,8 @@ func TestReceivePackHookSeesQuarantinedObjectsAndCanRejectBeforePromotion(t *tes
t.Fatalf("unexpected receive-pack output %q", got)
}
- if _, err := repo.Refs().Resolve("refs/heads/main"); err == nil {
+ _, err = repo.Refs().Resolve("refs/heads/main")
+ if err == nil {
t.Fatal("refs/heads/main exists after hook rejection")
}
@@ -535,11 +543,13 @@ func TestReceivePackHookCanRejectSubsetOfNonAtomicDeleteOnlyPush(t *testing.T) {
t.Fatalf("unexpected receive-pack output %q", got)
}
- if _, err := repo.Refs().Resolve("refs/heads/main"); err != nil {
+ _, err = repo.Refs().Resolve("refs/heads/main")
+ if err != nil {
t.Fatalf("Resolve(main): %v", err)
}
- if _, err := repo.Refs().Resolve("refs/heads/topic"); err == nil {
+ _, err = repo.Refs().Resolve("refs/heads/topic")
+ if err == nil {
t.Fatal("refs/heads/topic still exists after successful delete")
}
})
diff --git a/receivepack/internal/service/apply.go b/receivepack/internal/service/apply.go
index c24d2a95..f802e0e8 100644
--- a/receivepack/internal/service/apply.go
+++ b/receivepack/internal/service/apply.go
@@ -15,6 +15,7 @@ func (service *Service) applyAtomic(result *Result, commands []Command) error {
err = queueWriteTransaction(tx, command)
if err != nil {
_ = tx.Abort()
+
fillCommandErrors(result, commands, err.Error())
return nil
diff --git a/receivepack/internal/service/execute.go b/receivepack/internal/service/execute.go
index 8febfc79..14468799 100644
--- a/receivepack/internal/service/execute.go
+++ b/receivepack/internal/service/execute.go
@@ -3,8 +3,6 @@ package service
import (
"context"
"os"
-
- "codeberg.org/lindenii/furgit/format/pack/ingest"
)
// Execute validates one receive-pack request, optionally ingests its pack into
@@ -13,68 +11,23 @@ func (service *Service) Execute(ctx context.Context, req *Request) (*Result, err
result := &Result{
Commands: make([]CommandResult, 0, len(req.Commands)),
}
+
var (
quarantineName string
quarantineRoot *os.Root
err error
)
- if req.PackExpected {
- if req.Pack == nil {
- result.UnpackError = "missing pack stream"
- fillCommandErrors(result, req.Commands, "missing pack stream")
-
- return result, nil
- }
-
- if service.opts.ObjectsRoot == nil {
- result.UnpackError = "objects root not configured"
- fillCommandErrors(result, req.Commands, "objects root not configured")
-
- return result, nil
- }
-
- quarantineName, quarantineRoot, err = service.createQuarantineRoot()
- if err != nil {
- result.UnpackError = err.Error()
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
+ 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)
}()
-
- quarantinePackRoot, err := service.openQuarantinePackRoot(quarantineRoot)
- if err != nil {
- result.UnpackError = err.Error()
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
-
- defer func() {
- _ = quarantinePackRoot.Close()
- }()
-
- ingested, err := ingest.Ingest(
- req.Pack,
- quarantinePackRoot,
- service.opts.Algorithm,
- true,
- true,
- service.opts.ExistingObjects,
- )
- if err != nil {
- result.UnpackError = err.Error()
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
-
- result.Ingest = &ingested
}
for _, command := range req.Commands {
@@ -90,75 +43,30 @@ func (service *Service) Execute(ctx context.Context, req *Request) (*Result, err
return result, nil
}
- allowedCommands := append([]Command(nil), req.Commands...)
- allowedIndices := make([]int, 0, len(req.Commands))
- for index := range req.Commands {
- allowedIndices = append(allowedIndices, index)
- }
- rejected := make(map[int]string)
-
- if service.opts.Hook != nil {
- quarantinedObjects, err := service.openQuarantinedObjects(quarantineName)
- if err != nil {
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
-
- defer func() {
- _ = quarantinedObjects.Close()
- }()
-
- decisions, err := service.opts.Hook(ctx, HookRequest{
- Refs: service.opts.Refs,
- ExistingObjects: service.opts.ExistingObjects,
- QuarantinedObjects: quarantinedObjects,
- Updates: buildHookUpdates(req.Commands),
- PushOptions: append([]string(nil), req.PushOptions...),
- })
- if err != nil {
- fillCommandErrors(result, req.Commands, err.Error())
-
- return result, nil
- }
-
- if len(decisions) != len(req.Commands) {
- fillCommandErrors(result, req.Commands, "hook returned wrong number of update decisions")
-
- return result, nil
- }
-
- allowedCommands = allowedCommands[:0]
- allowedIndices = allowedIndices[:0]
- for index, decision := range decisions {
- if decision.Accept {
- allowedCommands = append(allowedCommands, req.Commands[index])
- allowedIndices = append(allowedIndices, index)
+ allowedCommands, allowedIndices, rejected, ok, errText := service.runHook(
+ ctx,
+ req,
+ req.Commands,
+ quarantineName,
+ )
+ if !ok {
+ fillCommandErrors(result, req.Commands, errText)
- continue
- }
+ return result, nil
+ }
- message := decision.Message
+ 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 = "rejected by hook"
+ message = "atomic push rejected by hook"
}
- rejected[index] = message
+ result.Commands = append(result.Commands, resultForHookRejection(command, message))
}
- 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
- }
+ return result, nil
}
if len(allowedCommands) == 0 {
@@ -181,6 +89,7 @@ func (service *Service) Execute(ctx context.Context, req *Request) (*Result, err
if req.Atomic {
subresult := &Result{}
+
err := service.applyAtomic(subresult, allowedCommands)
if err != nil {
return result, err
@@ -193,6 +102,7 @@ func (service *Service) Execute(ctx context.Context, req *Request) (*Result, err
}
subresult := &Result{}
+
err = service.applyBatch(subresult, allowedCommands)
if err != nil {
return result, err
diff --git a/receivepack/internal/service/ingest_quarantine.go b/receivepack/internal/service/ingest_quarantine.go
new file mode 100644
index 00000000..bf918c6d
--- /dev/null
+++ b/receivepack/internal/service/ingest_quarantine.go
@@ -0,0 +1,75 @@
+package service
+
+import (
+ "os"
+
+ "codeberg.org/lindenii/furgit/format/pack/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 {
+ result.UnpackError = "missing pack stream"
+ fillCommandErrors(result, commands, "missing pack stream")
+
+ return "", nil, false
+ }
+
+ if service.opts.ObjectsRoot == nil {
+ result.UnpackError = "objects root not configured"
+ fillCommandErrors(result, commands, "objects root not configured")
+
+ return "", nil, false
+ }
+
+ quarantineName, quarantineRoot, err := service.createQuarantineRoot()
+ if err != nil {
+ result.UnpackError = err.Error()
+ fillCommandErrors(result, commands, err.Error())
+
+ return "", nil, false
+ }
+
+ quarantinePackRoot, err := service.openQuarantinePackRoot(quarantineRoot)
+ if err != nil {
+ result.UnpackError = err.Error()
+ fillCommandErrors(result, commands, err.Error())
+
+ _ = quarantineRoot.Close()
+ _ = service.opts.ObjectsRoot.RemoveAll(quarantineName)
+
+ return "", nil, false
+ }
+
+ ingested, err := ingest.Ingest(
+ req.Pack,
+ quarantinePackRoot,
+ service.opts.Algorithm,
+ true,
+ true,
+ service.opts.ExistingObjects,
+ )
+
+ _ = quarantinePackRoot.Close()
+
+ if err != nil {
+ result.UnpackError = err.Error()
+ fillCommandErrors(result, commands, err.Error())
+
+ _ = quarantineRoot.Close()
+ _ = service.opts.ObjectsRoot.RemoveAll(quarantineName)
+
+ return "", nil, false
+ }
+
+ result.Ingest = &ingested
+
+ return quarantineName, quarantineRoot, true
+}
diff --git a/receivepack/internal/service/quarantine.go b/receivepack/internal/service/quarantine.go
index f263186b..97a85959 100644
--- a/receivepack/internal/service/quarantine.go
+++ b/receivepack/internal/service/quarantine.go
@@ -169,16 +169,17 @@ func finalizeQuarantineFile(
return applyPromotedFilePermissions(root, dst, perms)
case !errors.Is(err, fs.ErrExist):
_, statErr := root.Stat(dst)
- if statErr == nil {
+ switch {
+ case statErr == nil:
err = fs.ErrExist
- } else if errors.Is(statErr, fs.ErrNotExist) {
+ case errors.Is(statErr, fs.ErrNotExist):
renameErr := root.Rename(src, dst)
if renameErr == nil {
return applyPromotedFilePermissions(root, dst, perms)
}
err = renameErr
- } else {
+ default:
_ = root.Remove(src)
return statErr
diff --git a/receivepack/internal/service/quarantine_test.go b/receivepack/internal/service/quarantine_test.go
index b28868ed..0bab3728 100644
--- a/receivepack/internal/service/quarantine_test.go
+++ b/receivepack/internal/service/quarantine_test.go
@@ -1,4 +1,6 @@
-package service
+package service //nolint:testpackage
+
+// because we need access to quarantine internals
import (
"os"
@@ -9,167 +11,161 @@ import (
"codeberg.org/lindenii/furgit/objectstore/memory"
)
-func TestPromoteQuarantineAppliesConfiguredPermissions(t *testing.T) {
- t.Parallel()
+type quarantineFixture struct {
+ svc *Service
+ objectsRoot *os.Root
+ quarantineName string
+ quarantineRoot *os.Root
+}
- objectsDir := t.TempDir()
- objectsRoot, err := os.OpenRoot(objectsDir)
+func newQuarantineFixture(tb testing.TB, opts Options) *quarantineFixture {
+ tb.Helper()
+
+ objectsRoot, err := os.OpenRoot(tb.TempDir())
if err != nil {
- t.Fatalf("os.OpenRoot: %v", err)
+ tb.Fatalf("os.OpenRoot: %v", err)
}
- t.Cleanup(func() {
+ tb.Cleanup(func() {
_ = objectsRoot.Close()
})
- svc := New(Options{
- Algorithm: objectid.AlgorithmSHA1,
- ExistingObjects: memory.New(objectid.AlgorithmSHA1),
- ObjectsRoot: objectsRoot,
- PromotedObjectPermissions: &PromotedObjectPermissions{
- DirMode: 0o751,
- FileMode: 0o640,
- },
- })
+ opts.Algorithm = objectid.AlgorithmSHA1
+ opts.ExistingObjects = memory.New(objectid.AlgorithmSHA1)
+ opts.ObjectsRoot = objectsRoot
+
+ svc := New(opts)
quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
if err != nil {
- t.Fatalf("createQuarantineRoot: %v", err)
+ tb.Fatalf("createQuarantineRoot: %v", err)
}
- t.Cleanup(func() {
+ tb.Cleanup(func() {
_ = quarantineRoot.Close()
_ = objectsRoot.RemoveAll(quarantineName)
})
- if err := quarantineRoot.Mkdir("ab", 0o700); err != nil {
- t.Fatalf("Mkdir(ab): %v", err)
+ return &quarantineFixture{
+ svc: svc,
+ objectsRoot: objectsRoot,
+ quarantineName: quarantineName,
+ quarantineRoot: quarantineRoot,
}
+}
- if err := quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte("payload"), 0o600); err != nil {
- t.Fatalf("WriteFile(quarantine loose): %v", err)
- }
+func writeMatchingPromotedFile(
+ tb testing.TB,
+ quarantineRoot, objectsRoot *os.Root,
+ dir, name, payload string,
+) {
+ tb.Helper()
- if err := svc.promoteQuarantine(quarantineName, quarantineRoot); err != nil {
- t.Fatalf("promoteQuarantine: %v", err)
+ err := quarantineRoot.Mkdir(dir, 0o755)
+ if err != nil {
+ tb.Fatalf("Mkdir(%s): %v", dir, err)
}
- dirInfo, err := objectsRoot.Stat("ab")
+ err = objectsRoot.Mkdir(dir, 0o755)
if err != nil {
- t.Fatalf("Stat(ab): %v", err)
+ tb.Fatalf("Mkdir(dst %s): %v", dir, err)
}
- if got := dirInfo.Mode().Perm(); got != 0o751 {
- t.Fatalf("dir mode = %o, want 751", got)
- }
+ rel := path.Join(dir, name)
- fileInfo, err := objectsRoot.Stat(path.Join("ab", "cdef"))
+ err = quarantineRoot.WriteFile(rel, []byte(payload), 0o644)
if err != nil {
- t.Fatalf("Stat(ab/cdef): %v", err)
+ tb.Fatalf("WriteFile(quarantine %s): %v", rel, err)
}
- if got := fileInfo.Mode().Perm(); got != 0o640 {
- t.Fatalf("file mode = %o, want 640", got)
+ err = objectsRoot.WriteFile(rel, []byte(payload), 0o644)
+ if err != nil {
+ tb.Fatalf("WriteFile(permanent %s): %v", rel, err)
}
}
-func TestPromoteQuarantineTreatsExistingLooseObjectAsSuccess(t *testing.T) {
+func TestPromoteQuarantineAppliesConfiguredPermissions(t *testing.T) {
t.Parallel()
- objectsDir := t.TempDir()
- objectsRoot, err := os.OpenRoot(objectsDir)
- if err != nil {
- t.Fatalf("os.OpenRoot: %v", err)
- }
-
- t.Cleanup(func() {
- _ = objectsRoot.Close()
- })
-
- svc := New(Options{
- Algorithm: objectid.AlgorithmSHA1,
- ExistingObjects: memory.New(objectid.AlgorithmSHA1),
- ObjectsRoot: objectsRoot,
+ fx := newQuarantineFixture(t, Options{
+ PromotedObjectPermissions: &PromotedObjectPermissions{
+ DirMode: 0o751,
+ FileMode: 0o640,
+ },
})
- quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
+ err := fx.quarantineRoot.Mkdir("ab", 0o700)
if err != nil {
- t.Fatalf("createQuarantineRoot: %v", err)
+ t.Fatalf("Mkdir(ab): %v", err)
}
- t.Cleanup(func() {
- _ = quarantineRoot.Close()
- _ = objectsRoot.RemoveAll(quarantineName)
- })
+ err = fx.quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte("payload"), 0o600)
+ if err != nil {
+ t.Fatalf("WriteFile(quarantine loose): %v", err)
+ }
- if err := quarantineRoot.Mkdir("ab", 0o755); err != nil {
- t.Fatalf("Mkdir(ab): %v", err)
+ err = fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
+ if err != nil {
+ t.Fatalf("promoteQuarantine: %v", err)
}
- if err := objectsRoot.Mkdir("ab", 0o755); err != nil {
- t.Fatalf("Mkdir(dst ab): %v", err)
+ dirInfo, err := fx.objectsRoot.Stat("ab")
+ if err != nil {
+ t.Fatalf("Stat(ab): %v", err)
}
- const payload = "same object bytes"
- if err := quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte(payload), 0o644); err != nil {
- t.Fatalf("WriteFile(quarantine loose): %v", err)
+ if got := dirInfo.Mode().Perm(); got != 0o751 {
+ t.Fatalf("dir mode = %o, want 751", got)
}
- if err := objectsRoot.WriteFile(path.Join("ab", "cdef"), []byte(payload), 0o644); err != nil {
- t.Fatalf("WriteFile(permanent loose): %v", err)
+ fileInfo, err := fx.objectsRoot.Stat(path.Join("ab", "cdef"))
+ if err != nil {
+ t.Fatalf("Stat(ab/cdef): %v", err)
}
- if err := svc.promoteQuarantine(quarantineName, quarantineRoot); err != nil {
- t.Fatalf("promoteQuarantine: %v", err)
+ if got := fileInfo.Mode().Perm(); got != 0o640 {
+ t.Fatalf("file mode = %o, want 640", got)
}
}
-func TestPromoteQuarantineRejectsDifferentExistingPackFile(t *testing.T) {
+func TestPromoteQuarantineTreatsExistingLooseObjectAsSuccess(t *testing.T) {
t.Parallel()
- objectsDir := t.TempDir()
- objectsRoot, err := os.OpenRoot(objectsDir)
+ 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("os.OpenRoot: %v", err)
+ t.Fatalf("promoteQuarantine: %v", err)
}
+}
- t.Cleanup(func() {
- _ = objectsRoot.Close()
- })
+func TestPromoteQuarantineRejectsDifferentExistingPackFile(t *testing.T) {
+ t.Parallel()
- svc := New(Options{
- Algorithm: objectid.AlgorithmSHA1,
- ExistingObjects: memory.New(objectid.AlgorithmSHA1),
- ObjectsRoot: objectsRoot,
- })
+ fx := newQuarantineFixture(t, Options{})
- quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
+ err := fx.quarantineRoot.Mkdir("pack", 0o755)
if err != nil {
- t.Fatalf("createQuarantineRoot: %v", err)
- }
-
- t.Cleanup(func() {
- _ = quarantineRoot.Close()
- _ = objectsRoot.RemoveAll(quarantineName)
- })
-
- if err := quarantineRoot.Mkdir("pack", 0o755); err != nil {
t.Fatalf("Mkdir(pack): %v", err)
}
- if err := objectsRoot.Mkdir("pack", 0o755); err != nil {
+ err = fx.objectsRoot.Mkdir("pack", 0o755)
+ if err != nil {
t.Fatalf("Mkdir(dst pack): %v", err)
}
- if err := quarantineRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte("new bytes"), 0o644); err != nil {
+ err = fx.quarantineRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte("new bytes"), 0o644)
+ if err != nil {
t.Fatalf("WriteFile(quarantine pack): %v", err)
}
- if err := objectsRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte("old bytes"), 0o644); err != nil {
+ 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 = svc.promoteQuarantine(quarantineName, quarantineRoot)
+ err = fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
if err == nil {
t.Fatal("promoteQuarantine unexpectedly succeeded")
}
@@ -178,50 +174,11 @@ func TestPromoteQuarantineRejectsDifferentExistingPackFile(t *testing.T) {
func TestPromoteQuarantineAcceptsMatchingExistingPackFile(t *testing.T) {
t.Parallel()
- objectsDir := t.TempDir()
- objectsRoot, err := os.OpenRoot(objectsDir)
- if err != nil {
- t.Fatalf("os.OpenRoot: %v", err)
- }
-
- t.Cleanup(func() {
- _ = objectsRoot.Close()
- })
-
- svc := New(Options{
- Algorithm: objectid.AlgorithmSHA1,
- ExistingObjects: memory.New(objectid.AlgorithmSHA1),
- ObjectsRoot: objectsRoot,
- })
+ fx := newQuarantineFixture(t, Options{})
+ writeMatchingPromotedFile(t, fx.quarantineRoot, fx.objectsRoot, "pack", "pack-a.pack", "identical pack bytes")
- quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
+ err := fx.svc.promoteQuarantine(fx.quarantineName, fx.quarantineRoot)
if err != nil {
- t.Fatalf("createQuarantineRoot: %v", err)
- }
-
- t.Cleanup(func() {
- _ = quarantineRoot.Close()
- _ = objectsRoot.RemoveAll(quarantineName)
- })
-
- if err := quarantineRoot.Mkdir("pack", 0o755); err != nil {
- t.Fatalf("Mkdir(pack): %v", err)
- }
-
- if err := objectsRoot.Mkdir("pack", 0o755); err != nil {
- t.Fatalf("Mkdir(dst pack): %v", err)
- }
-
- const payload = "identical pack bytes"
- if err := quarantineRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte(payload), 0o644); err != nil {
- t.Fatalf("WriteFile(quarantine pack): %v", err)
- }
-
- if err := objectsRoot.WriteFile(path.Join("pack", "pack-a.pack"), []byte(payload), 0o644); err != nil {
- t.Fatalf("WriteFile(permanent pack): %v", err)
- }
-
- if err := svc.promoteQuarantine(quarantineName, quarantineRoot); err != nil {
t.Fatalf("promoteQuarantine: %v", err)
}
}
diff --git a/receivepack/internal/service/run_hook.go b/receivepack/internal/service/run_hook.go
new file mode 100644
index 00000000..cf934664
--- /dev/null
+++ b/receivepack/internal/service/run_hook.go
@@ -0,0 +1,73 @@
+package service
+
+import "context"
+
+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, ""
+ }
+
+ quarantinedObjects, err := service.openQuarantinedObjects(quarantineName)
+ if err != nil {
+ return nil, nil, nil, false, err.Error()
+ }
+
+ defer func() {
+ _ = quarantinedObjects.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...),
+ })
+ if err != nil {
+ return nil, nil, nil, false, err.Error()
+ }
+
+ if len(decisions) != len(commands) {
+ 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
+ }
+
+ return allowedCommands, allowedIndices, rejected, true, ""
+}