diff options
| author | 2026-03-06 21:19:56 +0800 | |
|---|---|---|
| committer | 2026-03-07 00:34:30 +0800 | |
| commit | 01d15bccf3b1dcc51516b1f64d50950b31d7f8fb (patch) | |
| tree | e491fcc762c67c1ef4ce54faafc5dafdb734ae8a /objectstore | |
| parent | objectstored/refstore: Weird ireturn behavior (diff) | |
| signature | No 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.go | 13 | ||||
| -rw-r--r-- | objectstore/loose/read_test.go | 4 | ||||
| -rw-r--r-- | objectstore/loose/write_test.go | 12 | ||||
| -rw-r--r-- | objectstore/memory/add.go | 21 | ||||
| -rw-r--r-- | objectstore/memory/algorithm.go | 8 | ||||
| -rw-r--r-- | objectstore/memory/doc.go | 2 | ||||
| -rw-r--r-- | objectstore/memory/object.go | 9 | ||||
| -rw-r--r-- | objectstore/memory/read_bytes.go | 37 | ||||
| -rw-r--r-- | objectstore/memory/read_header.go | 17 | ||||
| -rw-r--r-- | objectstore/memory/read_reader.go | 29 | ||||
| -rw-r--r-- | objectstore/memory/read_size.go | 13 | ||||
| -rw-r--r-- | objectstore/memory/store.go | 24 | ||||
| -rw-r--r-- | objectstore/objectstore.go | 1 | ||||
| -rw-r--r-- | objectstore/packed/helpers_test.go | 13 | ||||
| -rw-r--r-- | objectstore/packed/read_test.go | 40 |
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 { |
