ref: 446993c94dc34c0374e00f3f5f21ece72b18a9f6
parent: d86d30116b7509e616d4dfedc8b9aca2db5ae3e8
author: Runxi Yu <me@runxiyu.org>
date: Sat Mar 7 16:05:30 EST 2026
receivepack: Set permissions properly
--- a/receivepack/internal/service/options.go
+++ b/receivepack/internal/service/options.go
@@ -1,6 +1,7 @@
package service
import (
+ "io/fs"
"os"
"codeberg.org/lindenii/furgit/objectid"
@@ -8,11 +9,17 @@
"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
+ Algorithm objectid.Algorithm
+ Refs refstore.ReadWriteStore
+ ExistingObjects objectstore.Store
+ ObjectsRoot *os.Root
+ PromotedObjectPermissions *PromotedObjectPermissions
// TODO: Hook and such callbacks.
}
--- a/receivepack/internal/service/quarantine.go
+++ b/receivepack/internal/service/quarantine.go
@@ -71,6 +71,11 @@
return err
}
+ err = service.applyPromotedDirectoryPermissions(childRel)
+ if err != nil {+ return err
+ }
+
err = service.promoteQuarantineDir(quarantineName, quarantineRoot, childRel)
if err != nil {return err
@@ -84,6 +89,7 @@
path.Join(quarantineName, childRel),
childRel,
isLooseObjectShardPath(rel),
+ service.opts.PromotedObjectPermissions,
)
if err == nil {continue
@@ -126,7 +132,32 @@
return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')}
-func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck bool) error {+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++ {@@ -135,7 +166,7 @@
case err == nil:
_ = root.Remove(src)
- return nil
+ return applyPromotedFilePermissions(root, dst, perms)
case !errors.Is(err, fs.ErrExist):
_, statErr := root.Stat(dst)
if statErr == nil {@@ -143,7 +174,7 @@
} else if errors.Is(statErr, fs.ErrNotExist) {renameErr := root.Rename(src, dst)
if renameErr == nil {- return nil
+ return applyPromotedFilePermissions(root, dst, perms)
}
err = renameErr
@@ -163,7 +194,7 @@
if skipCollisionCheck {_ = root.Remove(src)
- return nil
+ return applyPromotedFilePermissions(root, dst, perms)
}
equal, vanished, cmpErr := compareRootFiles(root, src, dst)
@@ -185,7 +216,7 @@
_ = root.Remove(src)
- return nil
+ return applyPromotedFilePermissions(root, dst, perms)
}
}
--- a/receivepack/internal/service/quarantine_test.go
+++ b/receivepack/internal/service/quarantine_test.go
@@ -9,6 +9,70 @@
"codeberg.org/lindenii/furgit/objectstore/memory"
)
+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,
+ PromotedObjectPermissions: &PromotedObjectPermissions{+ DirMode: 0o751,
+ FileMode: 0o640,
+ },
+ })
+
+ quarantineName, quarantineRoot, err := svc.createQuarantineRoot()
+ if err != nil {+ t.Fatalf("createQuarantineRoot: %v", err)+ }
+
+ t.Cleanup(func() {+ _ = quarantineRoot.Close()
+ _ = objectsRoot.RemoveAll(quarantineName)
+ })
+
+ if err := quarantineRoot.Mkdir("ab", 0o700); err != nil {+ t.Fatalf("Mkdir(ab): %v", err)+ }
+
+ if err := quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte("payload"), 0o600); err != nil {+ t.Fatalf("WriteFile(quarantine loose): %v", err)+ }
+
+ if err := svc.promoteQuarantine(quarantineName, quarantineRoot); err != nil {+ t.Fatalf("promoteQuarantine: %v", err)+ }
+
+ dirInfo, err := 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 := 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()
--- a/receivepack/options.go
+++ b/receivepack/options.go
@@ -1,6 +1,7 @@
package receivepack
import (
+ "io/fs"
"os"
"codeberg.org/lindenii/furgit/objectid"
@@ -8,6 +9,13 @@
"codeberg.org/lindenii/furgit/refstore"
)
+// PromotedObjectPermissions configures the destination permissions applied to
+// objects and directories promoted out of quarantine.
+type PromotedObjectPermissions struct {+ DirMode fs.FileMode
+ FileMode fs.FileMode
+}
+
// Options configures one receive-pack invocation.
type Options struct {// GitProtocol is the raw Git protocol version string from the transport,
@@ -23,6 +31,9 @@
// 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
// TODO: Hook and policy callbacks.
}
--- a/receivepack/receivepack.go
+++ b/receivepack/receivepack.go
@@ -71,6 +71,9 @@
Refs: opts.Refs,
ExistingObjects: opts.ExistingObjects,
ObjectsRoot: opts.ObjectsRoot,
+ PromotedObjectPermissions: translatePromotedObjectPermissions(
+ opts.PromotedObjectPermissions,
+ ),
})
result, err := svc.Execute(ctx, serviceReq)
@@ -89,4 +92,17 @@
}
return nil
+}
+
+func translatePromotedObjectPermissions(
+ perms *PromotedObjectPermissions,
+) *service.PromotedObjectPermissions {+ if perms == nil {+ return nil
+ }
+
+ return &service.PromotedObjectPermissions{+ DirMode: perms.DirMode,
+ FileMode: perms.FileMode,
+ }
}
--
⑨