shithub: furgit

Download patch

ref: 0fb1520c3119ed6aedc5cb25098c0fd0b4cacf90
parent: c09ab35a972e0c9dd57fd4c931ef9bd80c91a20d
author: Runxi Yu <me@runxiyu.org>
date: Sat Mar 7 17:38:17 EST 2026

*: Package sorting and updates

--- /dev/null
+++ b/protocol/doc.go
@@ -1,0 +1,2 @@
+// Package protocol encapsulates network protocol implementations.
+package protocol
--- /dev/null
+++ b/protocol/v0v1/doc.go
@@ -1,0 +1,2 @@
+// Package v0v1 provides common constants and routines for the V0 and V1 protocols.
+package v0v1
--- a/receivepack/commands.go
+++ b/receivepack/commands.go
@@ -2,7 +2,7 @@
 
 import (
 	protoreceive "codeberg.org/lindenii/furgit/protocol/v0v1/server/receivepack"
-	"codeberg.org/lindenii/furgit/receivepack/internal/service"
+	"codeberg.org/lindenii/furgit/receivepack/service"
 )
 
 func translateCommands(commands []protoreceive.Command) []service.Command {
--- a/receivepack/hook.go
+++ b/receivepack/hook.go
@@ -6,7 +6,7 @@
 
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objectstore"
-	"codeberg.org/lindenii/furgit/receivepack/internal/service"
+	"codeberg.org/lindenii/furgit/receivepack/service"
 	"codeberg.org/lindenii/furgit/refstore"
 )
 
--- a/receivepack/internal/service/apply.go
+++ /dev/null
@@ -1,108 +1,0 @@
-package service
-
-import (
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/refstore"
-)
-
-func (service *Service) applyAtomic(result *Result, commands []Command) error {
-	tx, err := service.opts.Refs.BeginTransaction()
-	if err != nil {
-		return err
-	}
-
-	for _, command := range commands {
-		err = queueWriteTransaction(tx, command)
-		if err != nil {
-			_ = tx.Abort()
-
-			fillCommandErrors(result, commands, err.Error())
-
-			return nil
-		}
-	}
-
-	err = tx.Commit()
-	if err != nil {
-		fillCommandErrors(result, commands, err.Error())
-
-		return nil
-	}
-
-	result.Applied = true
-	for _, command := range commands {
-		result.Commands = append(result.Commands, successCommandResult(command))
-	}
-
-	return nil
-}
-
-func (service *Service) applyBatch(result *Result, commands []Command) error {
-	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 {
-		return err
-	}
-
-	appliedAny := false
-
-	for i, command := range commands {
-		item := successCommandResult(command)
-		if i < len(batchResults) && batchResults[i].Error != nil {
-			item.Error = batchResults[i].Error.Error()
-		} else {
-			appliedAny = true
-		}
-
-		result.Commands = append(result.Commands, item)
-	}
-
-	result.Applied = appliedAny
-
-	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),
-	}
-}
--- a/receivepack/internal/service/command.go
+++ /dev/null
@@ -1,32 +1,0 @@
-package service
-
-import "codeberg.org/lindenii/furgit/objectid"
-
-// 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
-}
--- a/receivepack/internal/service/command_result.go
+++ /dev/null
@@ -1,13 +1,0 @@
-package service
-
-import "codeberg.org/lindenii/furgit/objectid"
-
-// CommandResult is one per-command execution result.
-type CommandResult struct {
-	Name         string
-	Error        string
-	RefName      string
-	OldID        *objectid.ObjectID
-	NewID        *objectid.ObjectID
-	ForcedUpdate bool
-}
--- a/receivepack/internal/service/doc.go
+++ /dev/null
@@ -1,2 +1,0 @@
-// Package service implements the protocol-independent receive-pack service.
-package service
--- a/receivepack/internal/service/execute.go
+++ /dev/null
@@ -1,115 +1,0 @@
-package service
-
-import (
-	"context"
-	"os"
-)
-
-// 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 {
-		// Git migrates quarantined objects into permanent storage immediately
-		// before starting ref updates.
-		err = service.promoteQuarantine(quarantineName, quarantineRoot)
-		if err != nil {
-			result.UnpackError = err.Error()
-			fillCommandErrors(result, req.Commands, err.Error())
-
-			return result, nil
-		}
-	}
-
-	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
-}
--- a/receivepack/internal/service/hook.go
+++ /dev/null
@@ -1,37 +1,0 @@
-package service
-
-import (
-	"context"
-	"io"
-
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/objectstore"
-	"codeberg.org/lindenii/furgit/refstore"
-)
-
-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
-}
-
-type HookRequest struct {
-	Refs               refstore.ReadingStore
-	ExistingObjects    objectstore.Store
-	QuarantinedObjects objectstore.Store
-	Updates            []RefUpdate
-	PushOptions        []string
-	IO                 HookIO
-}
-
-type Hook func(context.Context, HookRequest) ([]UpdateDecision, error)
--- a/receivepack/internal/service/hook_apply.go
+++ /dev/null
@@ -1,44 +1,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
-}
--- a/receivepack/internal/service/ingest_quarantine.go
+++ /dev/null
@@ -1,75 +1,0 @@
-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
-}
--- a/receivepack/internal/service/options.go
+++ /dev/null
@@ -1,26 +1,0 @@
-package service
-
-import (
-	"io/fs"
-	"os"
-
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/objectstore"
-	"codeberg.org/lindenii/furgit/refstore"
-)
-
-type PromotedObjectPermissions struct {
-	DirMode  fs.FileMode
-	FileMode fs.FileMode
-}
-
-// Options configures one protocol-independent receive-pack service.
-type Options struct {
-	Algorithm                 objectid.Algorithm
-	Refs                      refstore.ReadWriteStore
-	ExistingObjects           objectstore.Store
-	ObjectsRoot               *os.Root
-	PromotedObjectPermissions *PromotedObjectPermissions
-	Hook                      Hook
-	HookIO                    HookIO
-}
--- a/receivepack/internal/service/quarantine.go
+++ /dev/null
@@ -1,269 +1,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.
-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
-		}
-	}
-}
--- a/receivepack/internal/service/quarantine_objects.go
+++ /dev/null
@@ -1,50 +1,0 @@
-package service
-
-import (
-	"os"
-
-	"codeberg.org/lindenii/furgit/objectstore"
-	"codeberg.org/lindenii/furgit/objectstore/loose"
-	"codeberg.org/lindenii/furgit/objectstore/memory"
-	objectmix "codeberg.org/lindenii/furgit/objectstore/mix"
-	"codeberg.org/lindenii/furgit/objectstore/packed"
-)
-
-func (service *Service) openQuarantinedObjects(quarantineName string) (objectstore.Store, error) {
-	if quarantineName == "" {
-		return memory.New(service.opts.Algorithm), nil
-	}
-
-	looseRoot, err := service.opts.ObjectsRoot.OpenRoot(quarantineName)
-	if err != nil {
-		return nil, err
-	}
-
-	looseStore, err := loose.New(looseRoot, service.opts.Algorithm)
-	if err != nil {
-		_ = looseRoot.Close()
-
-		return nil, err
-	}
-
-	packRoot, err := looseRoot.OpenRoot("pack")
-	if err == nil {
-		packedStore, packedErr := packed.New(packRoot, service.opts.Algorithm)
-		if packedErr != nil {
-			_ = packRoot.Close()
-			_ = looseStore.Close()
-
-			return nil, packedErr
-		}
-
-		return objectmix.New(looseStore, packedStore), nil
-	}
-
-	if !os.IsNotExist(err) {
-		_ = looseStore.Close()
-
-		return nil, err
-	}
-
-	return looseStore, nil
-}
--- a/receivepack/internal/service/quarantine_test.go
+++ /dev/null
@@ -1,184 +1,0 @@
-package service //nolint:testpackage
-
-// because we need access to quarantine internals
-
-import (
-	"os"
-	"path"
-	"testing"
-
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/objectstore/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)
-	}
-}
--- a/receivepack/internal/service/request.go
+++ /dev/null
@@ -1,13 +1,0 @@
-package service
-
-import "io"
-
-// Request is one protocol-independent receive-pack execution request.
-type Request struct {
-	Commands     []Command
-	PushOptions  []string
-	Atomic       bool
-	DeleteOnly   bool
-	PackExpected bool
-	Pack         io.Reader
-}
--- a/receivepack/internal/service/result.go
+++ /dev/null
@@ -1,14 +1,0 @@
-package service
-
-import (
-	"codeberg.org/lindenii/furgit/format/pack/ingest"
-)
-
-// Result is one receive-pack execution result.
-type Result struct {
-	UnpackError string
-	Commands    []CommandResult
-	Ingest      *ingest.Result
-	Planned     []PlannedUpdate
-	Applied     bool
-}
--- a/receivepack/internal/service/run_hook.go
+++ /dev/null
@@ -1,74 +1,0 @@
-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...),
-		IO:                 service.opts.HookIO,
-	})
-	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, ""
-}
--- a/receivepack/internal/service/service.go
+++ /dev/null
@@ -1,11 +1,0 @@
-package service
-
-// Service executes protocol-independent receive-pack requests.
-type Service struct {
-	opts Options
-}
-
-// New creates one receive-pack service.
-func New(opts Options) *Service {
-	return &Service{opts: opts}
-}
--- a/receivepack/internal/service/service_test.go
+++ /dev/null
@@ -1,99 +1,0 @@
-package service_test
-
-import (
-	"context"
-	"io/fs"
-	"os"
-	"strings"
-	"testing"
-
-	"codeberg.org/lindenii/furgit/internal/testgit"
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/objectstore/memory"
-	"codeberg.org/lindenii/furgit/receivepack/internal/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))
-		}
-	})
-}
--- a/receivepack/internal/service/update.go
+++ /dev/null
@@ -1,12 +1,0 @@
-package service
-
-import "codeberg.org/lindenii/furgit/objectid"
-
-// 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
-}
--- a/receivepack/permissions.go
+++ b/receivepack/permissions.go
@@ -3,7 +3,7 @@
 import (
 	"io/fs"
 
-	"codeberg.org/lindenii/furgit/receivepack/internal/service"
+	"codeberg.org/lindenii/furgit/receivepack/service"
 )
 
 // PromotedObjectPermissions configures the destination permissions applied to
--- a/receivepack/receivepack.go
+++ b/receivepack/receivepack.go
@@ -7,7 +7,7 @@
 	"codeberg.org/lindenii/furgit/format/pktline"
 	common "codeberg.org/lindenii/furgit/protocol/v0v1/server"
 	protoreceive "codeberg.org/lindenii/furgit/protocol/v0v1/server/receivepack"
-	"codeberg.org/lindenii/furgit/receivepack/internal/service"
+	"codeberg.org/lindenii/furgit/receivepack/service"
 )
 
 // TODO: Some more designing to do. In particular, we'd like to have access to
--- a/receivepack/results.go
+++ b/receivepack/results.go
@@ -2,7 +2,7 @@
 
 import (
 	protoreceive "codeberg.org/lindenii/furgit/protocol/v0v1/server/receivepack"
-	"codeberg.org/lindenii/furgit/receivepack/internal/service"
+	"codeberg.org/lindenii/furgit/receivepack/service"
 )
 
 func translateResult(result *service.Result) protoreceive.ReportStatusResult {
--- /dev/null
+++ b/receivepack/service/apply.go
@@ -1,0 +1,108 @@
+package service
+
+import (
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/refstore"
+)
+
+func (service *Service) applyAtomic(result *Result, commands []Command) error {
+	tx, err := service.opts.Refs.BeginTransaction()
+	if err != nil {
+		return err
+	}
+
+	for _, command := range commands {
+		err = queueWriteTransaction(tx, command)
+		if err != nil {
+			_ = tx.Abort()
+
+			fillCommandErrors(result, commands, err.Error())
+
+			return nil
+		}
+	}
+
+	err = tx.Commit()
+	if err != nil {
+		fillCommandErrors(result, commands, err.Error())
+
+		return nil
+	}
+
+	result.Applied = true
+	for _, command := range commands {
+		result.Commands = append(result.Commands, successCommandResult(command))
+	}
+
+	return nil
+}
+
+func (service *Service) applyBatch(result *Result, commands []Command) error {
+	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 {
+		return err
+	}
+
+	appliedAny := false
+
+	for i, command := range commands {
+		item := successCommandResult(command)
+		if i < len(batchResults) && batchResults[i].Error != nil {
+			item.Error = batchResults[i].Error.Error()
+		} else {
+			appliedAny = true
+		}
+
+		result.Commands = append(result.Commands, item)
+	}
+
+	result.Applied = appliedAny
+
+	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),
+	}
+}
--- /dev/null
+++ b/receivepack/service/command.go
@@ -1,0 +1,32 @@
+package service
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// 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
+}
--- /dev/null
+++ b/receivepack/service/command_result.go
@@ -1,0 +1,13 @@
+package service
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// CommandResult is one per-command execution result.
+type CommandResult struct {
+	Name         string
+	Error        string
+	RefName      string
+	OldID        *objectid.ObjectID
+	NewID        *objectid.ObjectID
+	ForcedUpdate bool
+}
--- /dev/null
+++ b/receivepack/service/doc.go
@@ -1,0 +1,2 @@
+// Package service implements the protocol-independent receive-pack service.
+package service
--- /dev/null
+++ b/receivepack/service/execute.go
@@ -1,0 +1,115 @@
+package service
+
+import (
+	"context"
+	"os"
+)
+
+// 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 {
+		// Git migrates quarantined objects into permanent storage immediately
+		// before starting ref updates.
+		err = service.promoteQuarantine(quarantineName, quarantineRoot)
+		if err != nil {
+			result.UnpackError = err.Error()
+			fillCommandErrors(result, req.Commands, err.Error())
+
+			return result, nil
+		}
+	}
+
+	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
+}
--- /dev/null
+++ b/receivepack/service/hook.go
@@ -1,0 +1,37 @@
+package service
+
+import (
+	"context"
+	"io"
+
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objectstore"
+	"codeberg.org/lindenii/furgit/refstore"
+)
+
+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
+}
+
+type HookRequest struct {
+	Refs               refstore.ReadingStore
+	ExistingObjects    objectstore.Store
+	QuarantinedObjects objectstore.Store
+	Updates            []RefUpdate
+	PushOptions        []string
+	IO                 HookIO
+}
+
+type Hook func(context.Context, HookRequest) ([]UpdateDecision, error)
--- /dev/null
+++ b/receivepack/service/hook_apply.go
@@ -1,0 +1,44 @@
+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
+}
--- /dev/null
+++ b/receivepack/service/ingest_quarantine.go
@@ -1,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
+}
--- /dev/null
+++ b/receivepack/service/options.go
@@ -1,0 +1,26 @@
+package service
+
+import (
+	"io/fs"
+	"os"
+
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objectstore"
+	"codeberg.org/lindenii/furgit/refstore"
+)
+
+type PromotedObjectPermissions struct {
+	DirMode  fs.FileMode
+	FileMode fs.FileMode
+}
+
+// Options configures one protocol-independent receive-pack service.
+type Options struct {
+	Algorithm                 objectid.Algorithm
+	Refs                      refstore.ReadWriteStore
+	ExistingObjects           objectstore.Store
+	ObjectsRoot               *os.Root
+	PromotedObjectPermissions *PromotedObjectPermissions
+	Hook                      Hook
+	HookIO                    HookIO
+}
--- /dev/null
+++ b/receivepack/service/quarantine.go
@@ -1,0 +1,269 @@
+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.
+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
+		}
+	}
+}
--- /dev/null
+++ b/receivepack/service/quarantine_objects.go
@@ -1,0 +1,50 @@
+package service
+
+import (
+	"os"
+
+	"codeberg.org/lindenii/furgit/objectstore"
+	"codeberg.org/lindenii/furgit/objectstore/loose"
+	"codeberg.org/lindenii/furgit/objectstore/memory"
+	objectmix "codeberg.org/lindenii/furgit/objectstore/mix"
+	"codeberg.org/lindenii/furgit/objectstore/packed"
+)
+
+func (service *Service) openQuarantinedObjects(quarantineName string) (objectstore.Store, error) {
+	if quarantineName == "" {
+		return memory.New(service.opts.Algorithm), nil
+	}
+
+	looseRoot, err := service.opts.ObjectsRoot.OpenRoot(quarantineName)
+	if err != nil {
+		return nil, err
+	}
+
+	looseStore, err := loose.New(looseRoot, service.opts.Algorithm)
+	if err != nil {
+		_ = looseRoot.Close()
+
+		return nil, err
+	}
+
+	packRoot, err := looseRoot.OpenRoot("pack")
+	if err == nil {
+		packedStore, packedErr := packed.New(packRoot, service.opts.Algorithm)
+		if packedErr != nil {
+			_ = packRoot.Close()
+			_ = looseStore.Close()
+
+			return nil, packedErr
+		}
+
+		return objectmix.New(looseStore, packedStore), nil
+	}
+
+	if !os.IsNotExist(err) {
+		_ = looseStore.Close()
+
+		return nil, err
+	}
+
+	return looseStore, nil
+}
--- /dev/null
+++ b/receivepack/service/quarantine_test.go
@@ -1,0 +1,184 @@
+package service //nolint:testpackage
+
+// because we need access to quarantine internals
+
+import (
+	"os"
+	"path"
+	"testing"
+
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objectstore/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)
+	}
+}
--- /dev/null
+++ b/receivepack/service/request.go
@@ -1,0 +1,13 @@
+package service
+
+import "io"
+
+// Request is one protocol-independent receive-pack execution request.
+type Request struct {
+	Commands     []Command
+	PushOptions  []string
+	Atomic       bool
+	DeleteOnly   bool
+	PackExpected bool
+	Pack         io.Reader
+}
--- /dev/null
+++ b/receivepack/service/result.go
@@ -1,0 +1,14 @@
+package service
+
+import (
+	"codeberg.org/lindenii/furgit/format/pack/ingest"
+)
+
+// Result is one receive-pack execution result.
+type Result struct {
+	UnpackError string
+	Commands    []CommandResult
+	Ingest      *ingest.Result
+	Planned     []PlannedUpdate
+	Applied     bool
+}
--- /dev/null
+++ b/receivepack/service/run_hook.go
@@ -1,0 +1,74 @@
+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...),
+		IO:                 service.opts.HookIO,
+	})
+	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, ""
+}
--- /dev/null
+++ b/receivepack/service/service.go
@@ -1,0 +1,11 @@
+package service
+
+// Service executes protocol-independent receive-pack requests.
+type Service struct {
+	opts Options
+}
+
+// New creates one receive-pack service.
+func New(opts Options) *Service {
+	return &Service{opts: opts}
+}
--- /dev/null
+++ b/receivepack/service/service_test.go
@@ -1,0 +1,99 @@
+package service_test
+
+import (
+	"context"
+	"io/fs"
+	"os"
+	"strings"
+	"testing"
+
+	"codeberg.org/lindenii/furgit/internal/testgit"
+	"codeberg.org/lindenii/furgit/objectid"
+	"codeberg.org/lindenii/furgit/objectstore/memory"
+	"codeberg.org/lindenii/furgit/receivepack/internal/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))
+		}
+	})
+}
--- /dev/null
+++ b/receivepack/service/update.go
@@ -1,0 +1,12 @@
+package service
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// 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
+}
--