aboutsummaryrefslogtreecommitdiff
path: root/objectstore
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-06 21:19:56 +0800
committerGravatar Runxi Yu2026-03-07 00:34:30 +0800
commit01d15bccf3b1dcc51516b1f64d50950b31d7f8fb (patch)
treee491fcc762c67c1ef4ce54faafc5dafdb734ae8a /objectstore
parentobjectstored/refstore: Weird ireturn behavior (diff)
signatureNo signature
Urgh I made some wrong amends and I'm too tired to separate the commits out this time
ancestor: Split out of reachability mergebase: Add merge base routines internal/commitquery: Add commit query context engine thingy internal/peel: Shared tag peeling errors: Shared object query errors internal/testgit: Add rooted repo helpers; remove raw path access objectstore/memory: Add in-memory object store objectid: Add Compare helper
Diffstat (limited to 'objectstore')
-rw-r--r--objectstore/loose/helpers_test.go13
-rw-r--r--objectstore/loose/read_test.go4
-rw-r--r--objectstore/loose/write_test.go12
-rw-r--r--objectstore/memory/add.go21
-rw-r--r--objectstore/memory/algorithm.go8
-rw-r--r--objectstore/memory/doc.go2
-rw-r--r--objectstore/memory/object.go9
-rw-r--r--objectstore/memory/read_bytes.go37
-rw-r--r--objectstore/memory/read_header.go17
-rw-r--r--objectstore/memory/read_reader.go29
-rw-r--r--objectstore/memory/read_size.go13
-rw-r--r--objectstore/memory/store.go24
-rw-r--r--objectstore/objectstore.go1
-rw-r--r--objectstore/packed/helpers_test.go13
-rw-r--r--objectstore/packed/read_test.go40
15 files changed, 196 insertions, 47 deletions
diff --git a/objectstore/loose/helpers_test.go b/objectstore/loose/helpers_test.go
index 4b0bb60e..6cc50163 100644
--- a/objectstore/loose/helpers_test.go
+++ b/objectstore/loose/helpers_test.go
@@ -2,8 +2,6 @@ package loose_test
import (
"io"
- "os"
- "path/filepath"
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
@@ -13,17 +11,10 @@ import (
"codeberg.org/lindenii/furgit/objecttype"
)
-func openLooseStore(t *testing.T, repoPath string, algo objectid.Algorithm) *loose.Store {
+func openLooseStore(t *testing.T, testRepo *testgit.TestRepo, algo objectid.Algorithm) *loose.Store {
t.Helper()
- objectsPath := filepath.Join(repoPath, "objects")
-
- root, err := os.OpenRoot(objectsPath)
- if err != nil {
- t.Fatalf("OpenRoot(%q): %v", objectsPath, err)
- }
-
- t.Cleanup(func() { _ = root.Close() })
+ root := testRepo.OpenObjectsRoot(t)
store, err := loose.New(root, algo)
if err != nil {
diff --git a/objectstore/loose/read_test.go b/objectstore/loose/read_test.go
index 1efc1682..44e25910 100644
--- a/objectstore/loose/read_test.go
+++ b/objectstore/loose/read_test.go
@@ -21,7 +21,7 @@ func TestLooseStoreReadAgainstGit(t *testing.T) {
_, treeID, commitID := testRepo.MakeCommit(t, "subject\n\nbody")
tagID := testRepo.TagAnnotated(t, "v1", commitID, "tag message")
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
tests := []struct {
name string
@@ -108,7 +108,7 @@ func TestLooseStoreErrors(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
notFoundID, err := objectid.ParseHex(algo, strings.Repeat("0", algo.HexLen()))
if err != nil {
diff --git a/objectstore/loose/write_test.go b/objectstore/loose/write_test.go
index 5604c5b0..a7b12622 100644
--- a/objectstore/loose/write_test.go
+++ b/objectstore/loose/write_test.go
@@ -14,7 +14,7 @@ func TestLooseStoreWriteReaderContentAgainstGit(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
content := []byte("written-by-content-reader\n")
expectedHex := testRepo.RunInput(t, content, "hash-object", "-t", "blob", "--stdin")
@@ -54,7 +54,7 @@ func TestLooseStoreWriteReaderFullAgainstGit(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
body := []byte("full-reader-body\n")
@@ -91,7 +91,7 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
t.Run("content overflow", func(t *testing.T) {
t.Parallel()
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
_, err := store.WriteReaderContent(objecttype.TypeBlob, 1, bytes.NewReader([]byte("hello")))
if err == nil {
@@ -102,7 +102,7 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
t.Run("content short", func(t *testing.T) {
t.Parallel()
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
_, err := store.WriteReaderContent(objecttype.TypeBlob, 5, bytes.NewReader([]byte("x")))
if err == nil {
@@ -113,7 +113,7 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
t.Run("full malformed header", func(t *testing.T) {
t.Parallel()
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
_, err := store.WriteReaderFull(bytes.NewReader([]byte("not-a-header")))
if err == nil {
@@ -124,7 +124,7 @@ func TestLooseStoreReaderValidationErrors(t *testing.T) {
t.Run("full size mismatch", func(t *testing.T) {
t.Parallel()
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
- store := openLooseStore(t, testRepo.Dir(), algo)
+ store := openLooseStore(t, testRepo, algo)
raw := []byte("blob 1\x00hello")
diff --git a/objectstore/memory/add.go b/objectstore/memory/add.go
new file mode 100644
index 00000000..80d0022f
--- /dev/null
+++ b/objectstore/memory/add.go
@@ -0,0 +1,21 @@
+package memory
+
+import (
+ "codeberg.org/lindenii/furgit/objectheader"
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+// AddObject stores one object body and returns its object ID.
+func (store *Store) AddObject(ty objecttype.Type, body []byte) objectid.ObjectID {
+ header, ok := objectheader.Encode(ty, int64(len(body)))
+ if !ok {
+ panic("failed to encode object header")
+ }
+
+ raw := append(append([]byte(nil), header...), body...)
+ id := store.algo.Sum(raw)
+ store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), body...)}
+
+ return id
+}
diff --git a/objectstore/memory/algorithm.go b/objectstore/memory/algorithm.go
new file mode 100644
index 00000000..db43272e
--- /dev/null
+++ b/objectstore/memory/algorithm.go
@@ -0,0 +1,8 @@
+package memory
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// Algorithm returns the object ID algorithm used by the store.
+func (store *Store) Algorithm() objectid.Algorithm {
+ return store.algo
+}
diff --git a/objectstore/memory/doc.go b/objectstore/memory/doc.go
new file mode 100644
index 00000000..cb40d466
--- /dev/null
+++ b/objectstore/memory/doc.go
@@ -0,0 +1,2 @@
+// Package memory provides one in-memory object store.
+package memory
diff --git a/objectstore/memory/object.go b/objectstore/memory/object.go
new file mode 100644
index 00000000..940af328
--- /dev/null
+++ b/objectstore/memory/object.go
@@ -0,0 +1,9 @@
+package memory
+
+import "codeberg.org/lindenii/furgit/objecttype"
+
+// storedObject is one in-memory object entry.
+type storedObject struct {
+ ty objecttype.Type
+ content []byte
+}
diff --git a/objectstore/memory/read_bytes.go b/objectstore/memory/read_bytes.go
new file mode 100644
index 00000000..31c5b3d1
--- /dev/null
+++ b/objectstore/memory/read_bytes.go
@@ -0,0 +1,37 @@
+package memory
+
+import (
+ "codeberg.org/lindenii/furgit/objectheader"
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objectstore"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+// ReadBytesFull reads one full object, including the object header.
+func (store *Store) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
+ obj, ok := store.objects[id]
+ if !ok {
+ return nil, objectstore.ErrObjectNotFound
+ }
+
+ header, ok := objectheader.Encode(obj.ty, int64(len(obj.content)))
+ if !ok {
+ panic("failed to encode object header")
+ }
+
+ raw := make([]byte, len(header)+len(obj.content))
+ copy(raw, header)
+ copy(raw[len(header):], obj.content)
+
+ return raw, nil
+}
+
+// ReadBytesContent reads one object body.
+func (store *Store) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
+ obj, ok := store.objects[id]
+ if !ok {
+ return objecttype.TypeInvalid, nil, objectstore.ErrObjectNotFound
+ }
+
+ return obj.ty, append([]byte(nil), obj.content...), nil
+}
diff --git a/objectstore/memory/read_header.go b/objectstore/memory/read_header.go
new file mode 100644
index 00000000..1d0aff15
--- /dev/null
+++ b/objectstore/memory/read_header.go
@@ -0,0 +1,17 @@
+package memory
+
+import (
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objectstore"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+// ReadHeader reads one object header.
+func (store *Store) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
+ obj, ok := store.objects[id]
+ if !ok {
+ return objecttype.TypeInvalid, 0, objectstore.ErrObjectNotFound
+ }
+
+ return obj.ty, int64(len(obj.content)), nil
+}
diff --git a/objectstore/memory/read_reader.go b/objectstore/memory/read_reader.go
new file mode 100644
index 00000000..2e3feda1
--- /dev/null
+++ b/objectstore/memory/read_reader.go
@@ -0,0 +1,29 @@
+package memory
+
+import (
+ "bytes"
+ "io"
+
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+// ReadReaderFull reads one full object through a reader.
+func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
+ raw, err := store.ReadBytesFull(id)
+ if err != nil {
+ return nil, err
+ }
+
+ return io.NopCloser(bytes.NewReader(raw)), nil
+}
+
+// ReadReaderContent reads one object body through a reader.
+func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
+ ty, content, err := store.ReadBytesContent(id)
+ if err != nil {
+ return objecttype.TypeInvalid, 0, nil, err
+ }
+
+ return ty, int64(len(content)), io.NopCloser(bytes.NewReader(content)), nil
+}
diff --git a/objectstore/memory/read_size.go b/objectstore/memory/read_size.go
new file mode 100644
index 00000000..3ca7789a
--- /dev/null
+++ b/objectstore/memory/read_size.go
@@ -0,0 +1,13 @@
+package memory
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// ReadSize reads one object size.
+func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {
+ _, size, err := store.ReadHeader(id)
+ if err != nil {
+ return 0, err
+ }
+
+ return size, nil
+}
diff --git a/objectstore/memory/store.go b/objectstore/memory/store.go
new file mode 100644
index 00000000..f7513094
--- /dev/null
+++ b/objectstore/memory/store.go
@@ -0,0 +1,24 @@
+package memory
+
+import (
+ "codeberg.org/lindenii/furgit/objectid"
+)
+
+// Store is one in-memory object store.
+type Store struct {
+ algo objectid.Algorithm
+ objects map[objectid.ObjectID]storedObject
+}
+
+// New builds one empty in-memory store for one object format.
+func New(algo objectid.Algorithm) *Store {
+ return &Store{
+ algo: algo,
+ objects: make(map[objectid.ObjectID]storedObject),
+ }
+}
+
+// Close closes the in-memory store.
+func (store *Store) Close() error {
+ return nil
+}
diff --git a/objectstore/objectstore.go b/objectstore/objectstore.go
index 58b091ef..a68175ac 100644
--- a/objectstore/objectstore.go
+++ b/objectstore/objectstore.go
@@ -11,6 +11,7 @@ import (
// ErrObjectNotFound indicates that an object does not exist in a backend.
// TODO: This might need to be an interface or otherwise be able to encapsulate multiple concrete backends'.
+// XXX: Don't remove this in favor of errors.ObjectMissingError yet due to pressure of allocation large error structs.
var ErrObjectNotFound = errors.New("objectstore: object not found")
// Store reads Git objects by object ID.
diff --git a/objectstore/packed/helpers_test.go b/objectstore/packed/helpers_test.go
index 1b517294..581c0dd7 100644
--- a/objectstore/packed/helpers_test.go
+++ b/objectstore/packed/helpers_test.go
@@ -3,8 +3,6 @@ package packed_test
import (
"fmt"
"io"
- "os"
- "path/filepath"
"strconv"
"strings"
"testing"
@@ -16,17 +14,10 @@ import (
"codeberg.org/lindenii/furgit/objecttype"
)
-func openPackedStore(t *testing.T, repoPath string, algo objectid.Algorithm) *packed.Store {
+func openPackedStore(t *testing.T, testRepo *testgit.TestRepo, algo objectid.Algorithm) *packed.Store {
t.Helper()
- packPath := filepath.Join(repoPath, "objects", "pack")
-
- root, err := os.OpenRoot(packPath)
- if err != nil {
- t.Fatalf("OpenRoot(%q): %v", packPath, err)
- }
-
- t.Cleanup(func() { _ = root.Close() })
+ root := testRepo.OpenPackRoot(t)
store, err := packed.New(root, algo)
if err != nil {
diff --git a/objectstore/packed/read_test.go b/objectstore/packed/read_test.go
index 02ef4e75..435bc350 100644
--- a/objectstore/packed/read_test.go
+++ b/objectstore/packed/read_test.go
@@ -4,8 +4,7 @@ import (
"bytes"
"errors"
"fmt"
- "os"
- "path/filepath"
+ "io/fs"
"strconv"
"strings"
"testing"
@@ -20,7 +19,7 @@ func TestPackedStoreReadAgainstGit(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo, ids := createPackedFixtureRepo(t, algo)
- store := openPackedStore(t, testRepo.Dir(), algo)
+ store := openPackedStore(t, testRepo, algo)
for _, id := range ids {
t.Run(id.String(), func(t *testing.T) {
@@ -106,7 +105,7 @@ func TestPackedStoreErrors(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo, _ := createPackedFixtureRepo(t, algo)
- store := openPackedStore(t, testRepo.Dir(), algo)
+ store := openPackedStore(t, testRepo, algo)
notFoundID, err := objectid.ParseHex(algo, strings.Repeat("0", algo.HexLen()))
if err != nil {
@@ -172,7 +171,7 @@ func TestPackedStoreNewValidation(t *testing.T) {
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo, _ := createPackedFixtureRepo(t, algo)
- store := openPackedStore(t, testRepo.Dir(), algo)
+ store := openPackedStore(t, testRepo, algo)
err := store.Close()
if err != nil {
@@ -190,14 +189,9 @@ func TestPackedStoreInvalidAlgorithm(t *testing.T) {
t.Parallel()
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: objectid.AlgorithmSHA1, Bare: true})
- root, err := os.OpenRoot(testRepo.Dir())
- if err != nil {
- t.Fatalf("OpenRoot(%q): %v", testRepo.Dir(), err)
- }
-
- t.Cleanup(func() { _ = root.Close() })
+ root := testRepo.OpenPackRoot(t)
- _, err = packed.New(root, objectid.AlgorithmUnknown)
+ _, err := packed.New(root, objectid.AlgorithmUnknown)
if !errors.Is(err, objectid.ErrInvalidAlgorithm) {
t.Fatalf("packed.New invalid algorithm error = %v", err)
}
@@ -227,7 +221,7 @@ func TestPackedStoreReadHeaderUsesResolvedObjectSizeForDelta(t *testing.T) {
testRepo.Repack(t, "-a", "-d", "-f", "--window=128", "--depth=128")
deltaID, wantResolvedSize := findDeltaObjectWithResolvedSizeMismatch(t, testRepo, algo)
- store := openPackedStore(t, testRepo.Dir(), algo)
+ store := openPackedStore(t, testRepo, algo)
_, gotSize, err := store.ReadHeader(deltaID)
if err != nil {
@@ -252,16 +246,28 @@ func TestPackedStoreReadHeaderUsesResolvedObjectSizeForDelta(t *testing.T) {
func findDeltaObjectWithResolvedSizeMismatch(t *testing.T, testRepo *testgit.TestRepo, algo objectid.Algorithm) (objectid.ObjectID, int64) {
t.Helper()
- idxFiles, err := filepath.Glob(filepath.Join(testRepo.Dir(), "objects", "pack", "*.idx"))
+ packRoot := testRepo.OpenPackRoot(t)
+
+ entries, err := fs.ReadDir(packRoot.FS(), ".")
if err != nil {
- t.Fatalf("Glob idx: %v", err)
+ t.Fatalf("ReadDir(pack): %v", err)
+ }
+
+ var idxName string
+
+ for _, entry := range entries {
+ if strings.HasSuffix(entry.Name(), ".idx") {
+ idxName = entry.Name()
+
+ break
+ }
}
- if len(idxFiles) == 0 {
+ if idxName == "" {
t.Fatalf("no idx files found")
}
- verifyOut := testRepo.Run(t, "verify-pack", "-v", idxFiles[0])
+ verifyOut := testRepo.Run(t, "verify-pack", "-v", "objects/pack/"+idxName)
for line := range strings.SplitSeq(strings.TrimSpace(verifyOut), "\n") {
fields := strings.Fields(line)
if len(fields) < 7 {