aboutsummaryrefslogtreecommitdiff
path: root/reachability
diff options
context:
space:
mode:
Diffstat (limited to 'reachability')
-rw-r--r--reachability/ancestor.go122
-rw-r--r--reachability/errors.go39
-rw-r--r--reachability/helpers.go5
-rw-r--r--reachability/integration_test.go101
-rw-r--r--reachability/peel.go37
-rw-r--r--reachability/unit_test.go246
-rw-r--r--reachability/walk.go2
-rw-r--r--reachability/walk_expand_commits.go3
-rw-r--r--reachability/walk_expand_objects.go5
-rw-r--r--reachability/walk_verify.go3
10 files changed, 69 insertions, 494 deletions
diff --git a/reachability/ancestor.go b/reachability/ancestor.go
deleted file mode 100644
index 584ec0e3..00000000
--- a/reachability/ancestor.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package reachability
-
-import (
- "errors"
-
- commitgraphread "codeberg.org/lindenii/furgit/format/commitgraph/read"
- "codeberg.org/lindenii/furgit/objectid"
-)
-
-// IsAncestor reports whether ancestor is reachable from descendant via commit
-// parent edges.
-//
-// Both inputs are peeled through annotated tags before commit traversal.
-func (r *Reachability) IsAncestor(ancestor, descendant objectid.ObjectID) (bool, error) {
- ancestorCommit, err := r.peelRootToCommit(ancestor)
- if err != nil {
- return false, err
- }
-
- descendantCommit, err := r.peelRootToCommit(descendant)
- if err != nil {
- return false, err
- }
-
- if ancestorCommit == descendantCommit {
- return true, nil
- }
-
- graphResult, graphUsed, err := r.isAncestorGraph(ancestorCommit, descendantCommit)
- if err != nil {
- return false, err
- }
-
- if graphUsed {
- return graphResult, nil
- }
-
- walk := r.Walk(DomainCommits, nil, map[objectid.ObjectID]struct{}{descendantCommit: {}})
- for id := range walk.Seq() {
- if id == ancestorCommit {
- return true, nil
- }
- }
-
- err = walk.Err()
- if err != nil {
- return false, err
- }
-
- return false, nil
-}
-
-func (r *Reachability) isAncestorGraph(ancestor, descendant objectid.ObjectID) (bool, bool, error) {
- if r.graph == nil {
- return false, false, nil
- }
-
- ancestorPos, err := r.graph.Lookup(ancestor)
- if err != nil {
- var notFound *commitgraphread.NotFoundError
- if errors.As(err, &notFound) {
- return false, false, nil
- }
-
- return false, true, err
- }
-
- descendantPos, err := r.graph.Lookup(descendant)
- if err != nil {
- var notFound *commitgraphread.NotFoundError
- if errors.As(err, &notFound) {
- return false, false, nil
- }
-
- return false, true, err
- }
-
- ancestorCommit, err := r.graph.CommitAt(ancestorPos)
- if err != nil {
- return false, true, err
- }
-
- ancestorGeneration := ancestorCommit.GenerationV2
- stack := []commitgraphread.Position{descendantPos}
- visited := make(map[commitgraphread.Position]struct{}, 64)
-
- for len(stack) > 0 {
- pos := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
-
- if _, ok := visited[pos]; ok {
- continue
- }
-
- visited[pos] = struct{}{}
-
- if pos == ancestorPos {
- return true, true, nil
- }
-
- commit, err := r.graph.CommitAt(pos)
- if err != nil {
- return false, true, err
- }
-
- if commit.GenerationV2 < ancestorGeneration {
- continue
- }
-
- if commit.Parent1.Valid {
- stack = append(stack, commit.Parent1.Pos)
- }
-
- if commit.Parent2.Valid {
- stack = append(stack, commit.Parent2.Pos)
- }
-
- stack = append(stack, commit.ExtraParents...)
- }
-
- return false, true, nil
-}
diff --git a/reachability/errors.go b/reachability/errors.go
deleted file mode 100644
index 0f0c6047..00000000
--- a/reachability/errors.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package reachability
-
-import (
- "fmt"
-
- "codeberg.org/lindenii/furgit/objectid"
- "codeberg.org/lindenii/furgit/objecttype"
-)
-
-// ObjectMissingError indicates that a referenced object is absent from the store.
-type ObjectMissingError struct {
- OID objectid.ObjectID
-}
-
-func (e *ObjectMissingError) Error() string {
- return fmt.Sprintf("reachability: missing object %s", e.OID)
-}
-
-// ObjectTypeError indicates that a referenced object has a different type than
-// what traversal expected on that edge.
-type ObjectTypeError struct {
- OID objectid.ObjectID
- Got objecttype.Type
- Want objecttype.Type
-}
-
-func (e *ObjectTypeError) Error() string {
- gotName, gotOK := objecttype.Name(e.Got)
- if !gotOK {
- gotName = fmt.Sprintf("type(%d)", e.Got)
- }
-
- wantName, wantOK := objecttype.Name(e.Want)
- if !wantOK {
- wantName = fmt.Sprintf("type(%d)", e.Want)
- }
-
- return fmt.Sprintf("reachability: object %s has type %s, want %s", e.OID, gotName, wantName)
-}
diff --git a/reachability/helpers.go b/reachability/helpers.go
index 9fdc99d8..02c3c726 100644
--- a/reachability/helpers.go
+++ b/reachability/helpers.go
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
+ giterrors "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objectstore"
"codeberg.org/lindenii/furgit/objecttype"
@@ -39,7 +40,7 @@ func (r *Reachability) readHeaderType(id objectid.ObjectID) (objecttype.Type, er
ty, _, err := r.store.ReadHeader(id)
if err != nil {
if errors.Is(err, objectstore.ErrObjectNotFound) {
- return objecttype.TypeInvalid, &ObjectMissingError{OID: id}
+ return objecttype.TypeInvalid, &giterrors.ObjectMissingError{OID: id}
}
return objecttype.TypeInvalid, err
@@ -61,7 +62,7 @@ func (r *Reachability) readBytesContent(id objectid.ObjectID) ([]byte, error) {
_, content, err := r.store.ReadBytesContent(id)
if err != nil {
if errors.Is(err, objectstore.ErrObjectNotFound) {
- return nil, &ObjectMissingError{OID: id}
+ return nil, &giterrors.ObjectMissingError{OID: id}
}
return nil, err
diff --git a/reachability/integration_test.go b/reachability/integration_test.go
index c7c5c63d..6b043d92 100644
--- a/reachability/integration_test.go
+++ b/reachability/integration_test.go
@@ -3,17 +3,16 @@ package reachability_test
import (
"errors"
"fmt"
+ "io/fs"
"maps"
- "os"
- "path/filepath"
"slices"
"strings"
"testing"
+ giterrors "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/internal/testgit"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/reachability"
- "codeberg.org/lindenii/furgit/repository"
)
func TestWalkCommitsMatchesGitRevList(t *testing.T) {
@@ -163,51 +162,6 @@ func TestWalkObjectsMatchesGitRevListObjects(t *testing.T) {
})
}
-func TestIsAncestorMatchesGitMergeBase(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,
- RefFormat: "files",
- })
-
- _, tree1 := testRepo.MakeSingleFileTree(t, "one.txt", []byte("one\n"))
- c1 := testRepo.CommitTree(t, tree1, "c1")
-
- _, tree2 := testRepo.MakeSingleFileTree(t, "two.txt", []byte("two\n"))
- c2 := testRepo.CommitTree(t, tree2, "c2", c1)
-
- _, tree3 := testRepo.MakeSingleFileTree(t, "three.txt", []byte("three\n"))
- c3 := testRepo.CommitTree(t, tree3, "c3", c2)
-
- tag := testRepo.TagAnnotated(t, "tip", c2, "tip")
-
- r := openReachabilityFromTestRepo(t, testRepo)
-
- got, err := r.IsAncestor(c1, tag)
- if err != nil {
- t.Fatalf("IsAncestor(c1, tag): %v", err)
- }
-
- want := gitMergeBaseIsAncestor(t, testRepo, c1, c2)
- if got != want {
- t.Fatalf("IsAncestor(c1, tag)=%v, want %v", got, want)
- }
-
- got, err = r.IsAncestor(c3, c2)
- if err != nil {
- t.Fatalf("IsAncestor(c3, c2): %v", err)
- }
-
- want = gitMergeBaseIsAncestor(t, testRepo, c3, c2)
- if got != want {
- t.Fatalf("IsAncestor(c3, c2)=%v, want %v", got, want)
- }
- })
-}
-
func TestCheckConnectedMissingObject(t *testing.T) {
t.Parallel()
@@ -220,14 +174,11 @@ func TestCheckConnectedMissingObject(t *testing.T) {
_, treeID, commitID := testRepo.MakeCommit(t, "missing")
- err := os.Remove(looseObjectPath(testRepo.Dir(), treeID))
- if err != nil {
- t.Fatalf("remove tree object: %v", err)
- }
+ testRepo.RemoveLooseObject(t, treeID)
r := openReachabilityFromTestRepo(t, testRepo)
- err = r.CheckConnected(
+ err := r.CheckConnected(
reachability.DomainObjects,
nil,
map[objectid.ObjectID]struct{}{commitID: {}},
@@ -236,7 +187,7 @@ func TestCheckConnectedMissingObject(t *testing.T) {
t.Fatal("expected error")
}
- var missing *reachability.ObjectMissingError
+ var missing *giterrors.ObjectMissingError
if !errors.As(err, &missing) {
t.Fatalf("expected ObjectMissingError, got %T (%v)", err, err)
}
@@ -267,7 +218,7 @@ func TestWalkOnPackedOnlyRepo(t *testing.T) {
testRepo.Repack(t, "-ad")
testRepo.Run(t, "prune-packed")
- assertPackedOnly(t, testRepo.Dir())
+ assertPackedOnly(t, testRepo)
r := openReachabilityFromTestRepo(t, testRepo)
walk := r.Walk(
@@ -298,21 +249,7 @@ func TestWalkOnPackedOnlyRepo(t *testing.T) {
func openReachabilityFromTestRepo(t *testing.T, testRepo *testgit.TestRepo) *reachability.Reachability {
t.Helper()
- root, err := os.OpenRoot(testRepo.Dir())
- if err != nil {
- t.Fatalf("os.OpenRoot: %v", err)
- }
-
- t.Cleanup(func() { _ = root.Close() })
-
- repo, err := repository.Open(root)
- if err != nil {
- t.Fatalf("repository.Open: %v", err)
- }
-
- t.Cleanup(func() { _ = repo.Close() })
-
- return reachability.New(repo.Objects())
+ return reachability.New(testRepo.OpenObjectStore(t))
}
func oidSetFromSeq(seq func(func(objectid.ObjectID) bool)) map[objectid.ObjectID]struct{} {
@@ -379,14 +316,6 @@ func gitRevListSet(
return set
}
-func gitMergeBaseIsAncestor(t *testing.T, testRepo *testgit.TestRepo, a, b objectid.ObjectID) bool {
- t.Helper()
- // testgit.Run fatals on non-zero status, so we compare merge-base output.
- mb := testRepo.Run(t, "merge-base", a.String(), b.String())
-
- return mb == a.String()
-}
-
func sortedOIDStrings(set map[objectid.ObjectID]struct{}) []string {
out := make([]string, 0, len(set))
for id := range set {
@@ -398,18 +327,12 @@ func sortedOIDStrings(set map[objectid.ObjectID]struct{}) []string {
return out
}
-func looseObjectPath(repoDir string, id objectid.ObjectID) string {
- hex := id.String()
-
- return filepath.Join(repoDir, "objects", hex[:2], hex[2:])
-}
-
-func assertPackedOnly(t *testing.T, repoDir string) {
+func assertPackedOnly(t *testing.T, testRepo *testgit.TestRepo) {
t.Helper()
- objectsDir := filepath.Join(repoDir, "objects")
+ objectsRoot := testRepo.OpenObjectsRoot(t)
- entries, err := os.ReadDir(objectsDir)
+ entries, err := fs.ReadDir(objectsRoot.FS(), ".")
if err != nil {
t.Fatalf("ReadDir(objects): %v", err)
}
@@ -421,13 +344,13 @@ func assertPackedOnly(t *testing.T, repoDir string) {
}
if len(name) == 2 && isHexDirName(name) {
- subEntries, err := os.ReadDir(filepath.Join(objectsDir, name))
+ subEntries, err := fs.ReadDir(objectsRoot.FS(), name)
if err != nil {
t.Fatalf("ReadDir(objects/%s): %v", name, err)
}
if len(subEntries) != 0 {
- t.Fatalf("found loose objects in %s", filepath.Join(objectsDir, name))
+ t.Fatalf("found loose objects in objects/%s", name)
}
}
}
diff --git a/reachability/peel.go b/reachability/peel.go
deleted file mode 100644
index 5f24982e..00000000
--- a/reachability/peel.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package reachability
-
-import (
- "codeberg.org/lindenii/furgit/object"
- "codeberg.org/lindenii/furgit/objectid"
- "codeberg.org/lindenii/furgit/objecttype"
-)
-
-// peelRootToCommit peels annotated tags transitively until a commit is reached.
-func (r *Reachability) peelRootToCommit(id objectid.ObjectID) (objectid.ObjectID, error) {
- for {
- ty, err := r.readHeaderType(id)
- if err != nil {
- return objectid.ObjectID{}, err
- }
-
- if ty != objecttype.TypeTag {
- if ty != objecttype.TypeCommit {
- return objectid.ObjectID{}, &ObjectTypeError{OID: id, Got: ty, Want: objecttype.TypeCommit}
- }
-
- return id, nil
- }
-
- content, err := r.readBytesContent(id)
- if err != nil {
- return objectid.ObjectID{}, err
- }
-
- tag, err := object.ParseTag(content, id.Algorithm())
- if err != nil {
- return objectid.ObjectID{}, err
- }
-
- id = tag.Target
- }
-}
diff --git a/reachability/unit_test.go b/reachability/unit_test.go
index 2fef2b48..dea6d38b 100644
--- a/reachability/unit_test.go
+++ b/reachability/unit_test.go
@@ -1,109 +1,40 @@
package reachability_test
import (
- "bytes"
"errors"
"fmt"
- "io"
"maps"
"slices"
"testing"
+ giterrors "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/internal/testgit"
"codeberg.org/lindenii/furgit/object"
- "codeberg.org/lindenii/furgit/objectheader"
"codeberg.org/lindenii/furgit/objectid"
- "codeberg.org/lindenii/furgit/objectstore"
+ "codeberg.org/lindenii/furgit/objectstore/memory"
"codeberg.org/lindenii/furgit/objecttype"
"codeberg.org/lindenii/furgit/reachability"
)
-type storeObject struct {
- ty objecttype.Type
- content []byte
-}
-
type memStore struct {
- algo objectid.Algorithm
- objects map[objectid.ObjectID]storeObject
+ *memory.Store
+
readBytesByObjectID map[objectid.ObjectID]int
}
-func newMemStore(algo objectid.Algorithm) *memStore {
+// newCountingMemStore builds one in-memory store that records content-read
+// counts by object ID.
+func newCountingMemStore(algo objectid.Algorithm) *memStore {
return &memStore{
- algo: algo,
- objects: make(map[objectid.ObjectID]storeObject),
+ Store: memory.New(algo),
readBytesByObjectID: make(map[objectid.ObjectID]int),
}
}
-func (store *memStore) 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
-}
-
func (store *memStore) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
- obj, ok := store.objects[id]
- if !ok {
- return objecttype.TypeInvalid, nil, objectstore.ErrObjectNotFound
- }
-
store.readBytesByObjectID[id]++
- return obj.ty, append([]byte(nil), obj.content...), nil
-}
-
-func (store *memStore) 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
-}
-
-func (store *memStore) 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
-}
-
-func (store *memStore) ReadSize(id objectid.ObjectID) (int64, error) {
- _, size, err := store.ReadHeader(id)
- if err != nil {
- return 0, err
- }
-
- return size, nil
-}
-
-func (store *memStore) 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
-}
-
-func (store *memStore) Close() error {
- return nil
+ return store.Store.ReadBytesContent(id)
}
func commitBody(tree objectid.ObjectID, parents ...objectid.ObjectID) []byte {
@@ -151,17 +82,17 @@ func TestWalkDomainCommitsIncludesTagNodes(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ store := newCountingMemStore(algo)
+ blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n"))
+ tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("f"),
ID: blob,
}}}))
- commit1 := store.addObject(objecttype.TypeCommit, commitBody(tree))
- commit2 := store.addObject(objecttype.TypeCommit, commitBody(tree, commit1))
- tag1 := store.addObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit))
- tag2 := store.addObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag))
+ commit1 := store.AddObject(objecttype.TypeCommit, commitBody(tree))
+ commit2 := store.AddObject(objecttype.TypeCommit, commitBody(tree, commit1))
+ tag1 := store.AddObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit))
+ tag2 := store.AddObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag))
r := reachability.New(store)
walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag2: {}})
@@ -186,14 +117,14 @@ func TestWalkExcludesHavesCompletely(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ store := newCountingMemStore(algo)
+ blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n"))
+ tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("f"),
ID: blob,
}}}))
- commit := store.addObject(objecttype.TypeCommit, commitBody(tree))
+ commit := store.AddObject(objecttype.TypeCommit, commitBody(tree))
r := reachability.New(store)
walk := r.Walk(reachability.DomainCommits, map[objectid.ObjectID]struct{}{commit: {}}, map[objectid.ObjectID]struct{}{commit: {}})
@@ -215,14 +146,14 @@ func TestWalkDomainCommitsRejectsNonCommitRootAfterPeel(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ store := newCountingMemStore(algo)
+ blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n"))
+ tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("f"),
ID: blob,
}}}))
- tag := store.addObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree))
+ tag := store.AddObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree))
r := reachability.New(store)
walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag: {}})
@@ -233,7 +164,7 @@ func TestWalkDomainCommitsRejectsNonCommitRootAfterPeel(t *testing.T) {
t.Fatal("expected error")
}
- var typeErr *reachability.ObjectTypeError
+ var typeErr *giterrors.ObjectTypeError
if !errors.As(err, &typeErr) {
t.Fatalf("expected ObjectTypeError, got %T (%v)", err, err)
}
@@ -248,17 +179,17 @@ func TestWalkDomainCommitsHaveTagStopsTraversal(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ store := newCountingMemStore(algo)
+ blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n"))
+ tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("f"),
ID: blob,
}}}))
- commit1 := store.addObject(objecttype.TypeCommit, commitBody(tree))
- commit2 := store.addObject(objecttype.TypeCommit, commitBody(tree, commit1))
- tag1 := store.addObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit))
- tag2 := store.addObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag))
+ commit1 := store.AddObject(objecttype.TypeCommit, commitBody(tree))
+ commit2 := store.AddObject(objecttype.TypeCommit, commitBody(tree, commit1))
+ tag1 := store.AddObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit))
+ tag2 := store.AddObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag))
r := reachability.New(store)
walk := r.Walk(
@@ -287,23 +218,23 @@ func TestWalkDomainObjectsRecursesTreesAndSkipsBlobContentReads(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
+ store := newCountingMemStore(algo)
- blob1 := store.addObject(objecttype.TypeBlob, []byte("b1\n"))
- blob2 := store.addObject(objecttype.TypeBlob, []byte("b2\n"))
- gitlinkTarget := store.algo.Sum([]byte("external-submodule"))
+ blob1 := store.AddObject(objecttype.TypeBlob, []byte("b1\n"))
+ blob2 := store.AddObject(objecttype.TypeBlob, []byte("b2\n"))
+ gitlinkTarget := store.Algorithm().Sum([]byte("external-submodule"))
- subtree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ subtree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("nested"),
ID: blob2,
}}}))
- rootTree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{
+ rootTree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{
{Mode: object.FileModeRegular, Name: []byte("a"), ID: blob1},
{Mode: object.FileModeDir, Name: []byte("dir"), ID: subtree},
{Mode: object.FileModeGitlink, Name: []byte("submodule"), ID: gitlinkTarget},
}}))
- commit := store.addObject(objecttype.TypeCommit, commitBody(rootTree))
+ commit := store.AddObject(objecttype.TypeCommit, commitBody(rootTree))
r := reachability.New(store)
walk := r.Walk(reachability.DomainObjects, nil, map[objectid.ObjectID]struct{}{commit: {}})
@@ -332,15 +263,15 @@ func TestCheckConnectedReturnsConcreteMissingObject(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
+ store := newCountingMemStore(algo)
+ blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n"))
+ tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
Mode: object.FileModeRegular,
Name: []byte("f"),
ID: blob,
}}}))
- missingParent := store.algo.Sum([]byte("missing-parent"))
- commit := store.addObject(objecttype.TypeCommit, commitBody(tree, missingParent))
+ missingParent := store.Algorithm().Sum([]byte("missing-parent"))
+ commit := store.AddObject(objecttype.TypeCommit, commitBody(tree, missingParent))
r := reachability.New(store)
@@ -349,7 +280,7 @@ func TestCheckConnectedReturnsConcreteMissingObject(t *testing.T) {
t.Fatal("expected error")
}
- var missing *reachability.ObjectMissingError
+ var missing *giterrors.ObjectMissingError
if !errors.As(err, &missing) {
t.Fatalf("expected ObjectMissingError, got %T (%v)", err, err)
}
@@ -364,7 +295,7 @@ func TestWalkInvalidDomainReturnsPlainError(t *testing.T) {
t.Parallel()
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- r := reachability.New(newMemStore(algo))
+ r := reachability.New(newCountingMemStore(algo))
walk := r.Walk(reachability.Domain(99), nil, nil)
_ = collectSeq(walk.Seq())
@@ -376,78 +307,6 @@ func TestWalkInvalidDomainReturnsPlainError(t *testing.T) {
})
}
-func TestIsAncestor(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
- Mode: object.FileModeRegular,
- Name: []byte("f"),
- ID: blob,
- }}}))
- c1 := store.addObject(objecttype.TypeCommit, commitBody(tree))
- c2 := store.addObject(objecttype.TypeCommit, commitBody(tree, c1))
- otherBlob := store.addObject(objecttype.TypeBlob, []byte("other-blob\n"))
- otherTree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
- Mode: object.FileModeRegular,
- Name: []byte("g"),
- ID: otherBlob,
- }}}))
- c3 := store.addObject(objecttype.TypeCommit, commitBody(otherTree))
- tag := store.addObject(objecttype.TypeTag, tagBody(c2, objecttype.TypeCommit))
-
- r := reachability.New(store)
-
- ok, err := r.IsAncestor(c1, tag)
- if err != nil {
- t.Fatalf("IsAncestor(c1, tag): %v", err)
- }
-
- if !ok {
- t.Fatal("expected c1 to be ancestor of tag->c2")
- }
-
- ok, err = r.IsAncestor(c3, c2)
- if err != nil {
- t.Fatalf("IsAncestor(c3, c2): %v", err)
- }
-
- if ok {
- t.Fatal("did not expect c3 to be ancestor of c2")
- }
- })
-}
-
-func TestIsAncestorRejectsNonCommitAfterPeel(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
- blob := store.addObject(objecttype.TypeBlob, []byte("blob\n"))
- tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{
- Mode: object.FileModeRegular,
- Name: []byte("f"),
- ID: blob,
- }}}))
- commit := store.addObject(objecttype.TypeCommit, commitBody(tree))
- tagToTree := store.addObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree))
-
- r := reachability.New(store)
-
- _, err := r.IsAncestor(commit, tagToTree)
- if err == nil {
- t.Fatal("expected error")
- }
-
- var typeErr *reachability.ObjectTypeError
- if !errors.As(err, &typeErr) {
- t.Fatalf("expected ObjectTypeError, got %T (%v)", err, err)
- }
- })
-}
-
func mustSerializeTree(tb testing.TB, tree *object.Tree) []byte {
tb.Helper()
@@ -458,16 +317,3 @@ func mustSerializeTree(tb testing.TB, tree *object.Tree) []byte {
return body
}
-
-func (store *memStore) 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] = storeObject{ty: ty, content: append([]byte(nil), body...)}
-
- return id
-}
diff --git a/reachability/walk.go b/reachability/walk.go
index e6de8684..dc2f32fd 100644
--- a/reachability/walk.go
+++ b/reachability/walk.go
@@ -4,7 +4,7 @@ import (
"codeberg.org/lindenii/furgit/objectid"
)
-// Walk is one single-use iterator-style traversal.
+// Walk is one single-use iterator traversal.
type Walk struct {
reachability *Reachability
domain Domain
diff --git a/reachability/walk_expand_commits.go b/reachability/walk_expand_commits.go
index e72092f4..ac24be91 100644
--- a/reachability/walk_expand_commits.go
+++ b/reachability/walk_expand_commits.go
@@ -3,6 +3,7 @@ package reachability
import (
"fmt"
+ "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/object"
"codeberg.org/lindenii/furgit/objecttype"
)
@@ -63,7 +64,7 @@ func (walk *Walk) expandCommits(item walkItem) ([]walkItem, error) {
return []walkItem{{id: tag.Target, want: objecttype.TypeInvalid}}, nil
case objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeInvalid,
objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
- return nil, &ObjectTypeError{OID: item.id, Got: ty, Want: objecttype.TypeCommit}
+ return nil, &errors.ObjectTypeError{OID: item.id, Got: ty, Want: objecttype.TypeCommit}
}
return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
diff --git a/reachability/walk_expand_objects.go b/reachability/walk_expand_objects.go
index 9dc2ff80..1f634c26 100644
--- a/reachability/walk_expand_objects.go
+++ b/reachability/walk_expand_objects.go
@@ -3,6 +3,7 @@ package reachability
import (
"fmt"
+ "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/object"
"codeberg.org/lindenii/furgit/objecttype"
)
@@ -14,7 +15,7 @@ func (walk *Walk) expandObjects(item walkItem) ([]walkItem, error) {
}
if item.want != objecttype.TypeInvalid && ty != item.want {
- return nil, &ObjectTypeError{OID: item.id, Got: ty, Want: item.want}
+ return nil, &errors.ObjectTypeError{OID: item.id, Got: ty, Want: item.want}
}
switch ty {
@@ -76,7 +77,7 @@ func (walk *Walk) expandObjects(item walkItem) ([]walkItem, error) {
return []walkItem{{id: tag.Target, want: tag.TargetType}}, nil
case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
- return nil, &ObjectTypeError{OID: item.id, Got: ty, Want: item.want}
+ return nil, &errors.ObjectTypeError{OID: item.id, Got: ty, Want: item.want}
}
return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
diff --git a/reachability/walk_verify.go b/reachability/walk_verify.go
index 82eb7566..5b1b498d 100644
--- a/reachability/walk_verify.go
+++ b/reachability/walk_verify.go
@@ -1,6 +1,7 @@
package reachability
import (
+ "codeberg.org/lindenii/furgit/errors"
"codeberg.org/lindenii/furgit/object"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objecttype"
@@ -13,7 +14,7 @@ func (walk *Walk) validateCommitObject(id objectid.ObjectID) error {
}
if ty != objecttype.TypeCommit {
- return &ObjectTypeError{OID: id, Got: ty, Want: objecttype.TypeCommit}
+ return &errors.ObjectTypeError{OID: id, Got: ty, Want: objecttype.TypeCommit}
}
content, err := walk.readBytesContent(id)