diff options
| author | 2026-03-06 21:19:56 +0800 | |
|---|---|---|
| committer | 2026-03-07 00:34:30 +0800 | |
| commit | 01d15bccf3b1dcc51516b1f64d50950b31d7f8fb (patch) | |
| tree | e491fcc762c67c1ef4ce54faafc5dafdb734ae8a /reachability | |
| 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 'reachability')
| -rw-r--r-- | reachability/ancestor.go | 122 | ||||
| -rw-r--r-- | reachability/errors.go | 39 | ||||
| -rw-r--r-- | reachability/helpers.go | 5 | ||||
| -rw-r--r-- | reachability/integration_test.go | 101 | ||||
| -rw-r--r-- | reachability/peel.go | 37 | ||||
| -rw-r--r-- | reachability/unit_test.go | 246 | ||||
| -rw-r--r-- | reachability/walk.go | 2 | ||||
| -rw-r--r-- | reachability/walk_expand_commits.go | 3 | ||||
| -rw-r--r-- | reachability/walk_expand_objects.go | 5 | ||||
| -rw-r--r-- | reachability/walk_verify.go | 3 |
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, ¬Found) { - 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, ¬Found) { - 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) |
