aboutsummaryrefslogtreecommitdiff
path: root/reachability
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-06 10:59:53 +0800
committerGravatar Runxi Yu2026-03-06 10:59:53 +0800
commit95f8f3d45fe077042df4fd4afa73d4e419bc9974 (patch)
tree1e650b788eb4fbe5fc61ad0df700de58ebba40c5 /reachability
parentformat/commitgraph: Split layer files (diff)
signatureNo signature
reachability: Split walk files
Diffstat (limited to 'reachability')
-rw-r--r--reachability/walk.go299
-rw-r--r--reachability/walk_expand.go9
-rw-r--r--reachability/walk_expand_commits.go70
-rw-r--r--reachability/walk_expand_commits_graph.go57
-rw-r--r--reachability/walk_expand_objects.go83
-rw-r--r--reachability/walk_item.go11
-rw-r--r--reachability/walk_seq.go69
-rw-r--r--reachability/walk_stack.go16
-rw-r--r--reachability/walk_verify.go27
9 files changed, 342 insertions, 299 deletions
diff --git a/reachability/walk.go b/reachability/walk.go
index 4611e46e..13400e89 100644
--- a/reachability/walk.go
+++ b/reachability/walk.go
@@ -1,14 +1,7 @@
package reachability
import (
- "errors"
- "fmt"
- "iter"
-
- "codeberg.org/lindenii/furgit/format/commitgraph"
- "codeberg.org/lindenii/furgit/object"
"codeberg.org/lindenii/furgit/objectid"
- "codeberg.org/lindenii/furgit/objecttype"
)
// Walk is one single-use iterator-style traversal.
@@ -22,295 +15,3 @@ type Walk struct {
seqUsed bool
err error
}
-
-// Seq returns the traversal sequence. It is single-use.
-func (walk *Walk) Seq() iter.Seq[objectid.ObjectID] {
- if walk.seqUsed {
- return func(yield func(objectid.ObjectID) bool) {
- _ = yield
-
- if walk.err == nil {
- walk.err = errors.New("reachability: walk sequence already consumed")
- }
- }
- }
-
- walk.seqUsed = true
-
- return func(yield func(objectid.ObjectID) bool) {
- if walk.err != nil {
- return
- }
-
- stack := walk.initialStack()
-
- var err error
-
- visited := make(map[objectid.ObjectID]struct{}, len(stack))
- for len(stack) > 0 {
- item := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
-
- if containsOID(walk.haves, item.id) {
- continue
- }
-
- if _, ok := visited[item.id]; ok {
- continue
- }
-
- visited[item.id] = struct{}{}
-
- var next []walkItem
-
- next, err = walk.expand(item)
- if err != nil {
- walk.err = err
-
- return
- }
-
- if !yield(item.id) {
- return
- }
-
- stack = append(stack, next...)
- }
- }
-}
-
-// Err returns the terminal error, if any, once Seq has been consumed.
-func (walk *Walk) Err() error {
- return walk.err
-}
-
-type walkItem struct {
- id objectid.ObjectID
- want objecttype.Type
-}
-
-func (walk *Walk) initialStack() []walkItem {
- if len(walk.wants) == 0 {
- return nil
- }
-
- stack := make([]walkItem, 0, len(walk.wants))
- for want := range walk.wants {
- stack = append(stack, walkItem{id: want, want: objecttype.TypeInvalid})
- }
-
- return stack
-}
-
-func (walk *Walk) expand(item walkItem) ([]walkItem, error) {
- if walk.domain == DomainCommits {
- return walk.expandCommits(item)
- }
-
- return walk.expandObjects(item)
-}
-
-func (walk *Walk) expandCommits(item walkItem) ([]walkItem, error) {
- if walk.reachability.graph != nil { //nolint:nestif
- next, graphUsed, err := walk.expandCommitsFromGraph(item.id)
- if err != nil {
- return nil, err
- }
-
- if graphUsed && walk.strict {
- err = walk.validateCommitObject(item.id)
- if err != nil {
- return nil, err
- }
- }
-
- if graphUsed {
- return next, nil
- }
- }
-
- ty, err := walk.readHeaderType(item.id)
- if err != nil {
- return nil, err
- }
-
- switch ty {
- case objecttype.TypeCommit:
- content, err := walk.readBytesContent(item.id)
- if err != nil {
- return nil, err
- }
-
- commit, err := object.ParseCommit(content, item.id.Algorithm())
- if err != nil {
- return nil, err
- }
-
- next := make([]walkItem, 0, len(commit.Parents))
- for _, parent := range commit.Parents {
- next = append(next, walkItem{id: parent, want: objecttype.TypeInvalid})
- }
-
- return next, nil
- case objecttype.TypeTag:
- content, err := walk.readBytesContent(item.id)
- if err != nil {
- return nil, err
- }
-
- tag, err := object.ParseTag(content, item.id.Algorithm())
- if err != nil {
- return nil, err
- }
-
- return []walkItem{{id: tag.Target, want: objecttype.TypeInvalid}}, nil
- case objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeInvalid,
- objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
- return nil, &ErrObjectType{OID: item.id, Got: ty, Want: objecttype.TypeCommit}
- }
-
- return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
-}
-
-func (walk *Walk) validateCommitObject(id objectid.ObjectID) error {
- ty, err := walk.readHeaderType(id)
- if err != nil {
- return err
- }
-
- if ty != objecttype.TypeCommit {
- return &ErrObjectType{OID: id, Got: ty, Want: objecttype.TypeCommit}
- }
-
- content, err := walk.readBytesContent(id)
- if err != nil {
- return err
- }
-
- _, err = object.ParseCommit(content, id.Algorithm())
-
- return err
-}
-
-func (walk *Walk) expandCommitsFromGraph(id objectid.ObjectID) ([]walkItem, bool, error) {
- pos, err := walk.reachability.graph.Lookup(id)
- if err != nil {
- var notFound *commitgraph.ErrNotFound
- if errors.As(err, &notFound) {
- return nil, false, nil
- }
-
- return nil, true, err
- }
-
- commit, err := walk.reachability.graph.CommitAt(pos)
- if err != nil {
- return nil, true, err
- }
-
- next := make([]walkItem, 0, 2+len(commit.ExtraParents))
-
- if commit.Parent1.Valid {
- parentOID, err := walk.reachability.graph.OIDAt(commit.Parent1.Pos)
- if err != nil {
- return nil, true, err
- }
-
- next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
- }
-
- if commit.Parent2.Valid {
- parentOID, err := walk.reachability.graph.OIDAt(commit.Parent2.Pos)
- if err != nil {
- return nil, true, err
- }
-
- next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
- }
-
- for _, parentPos := range commit.ExtraParents {
- parentOID, err := walk.reachability.graph.OIDAt(parentPos)
- if err != nil {
- return nil, true, err
- }
-
- next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
- }
-
- return next, true, nil
-}
-
-func (walk *Walk) expandObjects(item walkItem) ([]walkItem, error) {
- ty, err := walk.readHeaderType(item.id)
- if err != nil {
- return nil, err
- }
-
- if item.want != objecttype.TypeInvalid && ty != item.want {
- return nil, &ErrObjectType{OID: item.id, Got: ty, Want: item.want}
- }
-
- switch ty {
- case objecttype.TypeBlob:
- return nil, nil
- case objecttype.TypeCommit:
- content, err := walk.readBytesContent(item.id)
- if err != nil {
- return nil, err
- }
-
- commit, err := object.ParseCommit(content, item.id.Algorithm())
- if err != nil {
- return nil, err
- }
-
- next := make([]walkItem, 0, len(commit.Parents)+1)
-
- next = append(next, walkItem{id: commit.Tree, want: objecttype.TypeTree})
- for _, parent := range commit.Parents {
- next = append(next, walkItem{id: parent, want: objecttype.TypeCommit})
- }
-
- return next, nil
- case objecttype.TypeTree:
- content, err := walk.readBytesContent(item.id)
- if err != nil {
- return nil, err
- }
-
- tree, err := object.ParseTree(content, item.id.Algorithm())
- if err != nil {
- return nil, err
- }
-
- next := make([]walkItem, 0, len(tree.Entries))
- for _, entry := range tree.Entries {
- switch entry.Mode {
- case object.FileModeGitlink:
- continue
- case object.FileModeDir:
- next = append(next, walkItem{id: entry.ID, want: objecttype.TypeTree})
- case object.FileModeRegular, object.FileModeExecutable, object.FileModeSymlink:
- next = append(next, walkItem{id: entry.ID, want: objecttype.TypeBlob})
- }
- }
-
- return next, nil
- case objecttype.TypeTag:
- content, err := walk.readBytesContent(item.id)
- if err != nil {
- return nil, err
- }
-
- tag, err := object.ParseTag(content, item.id.Algorithm())
- if err != nil {
- return nil, err
- }
-
- return []walkItem{{id: tag.Target, want: tag.TargetType}}, nil
- case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
- return nil, &ErrObjectType{OID: item.id, Got: ty, Want: item.want}
- }
-
- return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
-}
diff --git a/reachability/walk_expand.go b/reachability/walk_expand.go
new file mode 100644
index 00000000..e36534a2
--- /dev/null
+++ b/reachability/walk_expand.go
@@ -0,0 +1,9 @@
+package reachability
+
+func (walk *Walk) expand(item walkItem) ([]walkItem, error) {
+ if walk.domain == DomainCommits {
+ return walk.expandCommits(item)
+ }
+
+ return walk.expandObjects(item)
+}
diff --git a/reachability/walk_expand_commits.go b/reachability/walk_expand_commits.go
new file mode 100644
index 00000000..11c5c692
--- /dev/null
+++ b/reachability/walk_expand_commits.go
@@ -0,0 +1,70 @@
+package reachability
+
+import (
+ "fmt"
+
+ "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+func (walk *Walk) expandCommits(item walkItem) ([]walkItem, error) {
+ if walk.reachability.graph != nil { //nolint:nestif
+ next, graphUsed, err := walk.expandCommitsFromGraph(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ if graphUsed && walk.strict {
+ err = walk.validateCommitObject(item.id)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if graphUsed {
+ return next, nil
+ }
+ }
+
+ ty, err := walk.readHeaderType(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ switch ty {
+ case objecttype.TypeCommit:
+ content, err := walk.readBytesContent(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := object.ParseCommit(content, item.id.Algorithm())
+ if err != nil {
+ return nil, err
+ }
+
+ next := make([]walkItem, 0, len(commit.Parents))
+ for _, parent := range commit.Parents {
+ next = append(next, walkItem{id: parent, want: objecttype.TypeInvalid})
+ }
+
+ return next, nil
+ case objecttype.TypeTag:
+ content, err := walk.readBytesContent(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := object.ParseTag(content, item.id.Algorithm())
+ if err != nil {
+ return nil, err
+ }
+
+ return []walkItem{{id: tag.Target, want: objecttype.TypeInvalid}}, nil
+ case objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeInvalid,
+ objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
+ return nil, &ErrObjectType{OID: item.id, Got: ty, Want: objecttype.TypeCommit}
+ }
+
+ return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
+}
diff --git a/reachability/walk_expand_commits_graph.go b/reachability/walk_expand_commits_graph.go
new file mode 100644
index 00000000..15780c8e
--- /dev/null
+++ b/reachability/walk_expand_commits_graph.go
@@ -0,0 +1,57 @@
+package reachability
+
+import (
+ "errors"
+
+ "codeberg.org/lindenii/furgit/format/commitgraph"
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+func (walk *Walk) expandCommitsFromGraph(id objectid.ObjectID) ([]walkItem, bool, error) {
+ pos, err := walk.reachability.graph.Lookup(id)
+ if err != nil {
+ var notFound *commitgraph.ErrNotFound
+ if errors.As(err, &notFound) {
+ return nil, false, nil
+ }
+
+ return nil, true, err
+ }
+
+ commit, err := walk.reachability.graph.CommitAt(pos)
+ if err != nil {
+ return nil, true, err
+ }
+
+ next := make([]walkItem, 0, 2+len(commit.ExtraParents))
+
+ if commit.Parent1.Valid {
+ parentOID, err := walk.reachability.graph.OIDAt(commit.Parent1.Pos)
+ if err != nil {
+ return nil, true, err
+ }
+
+ next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
+ }
+
+ if commit.Parent2.Valid {
+ parentOID, err := walk.reachability.graph.OIDAt(commit.Parent2.Pos)
+ if err != nil {
+ return nil, true, err
+ }
+
+ next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
+ }
+
+ for _, parentPos := range commit.ExtraParents {
+ parentOID, err := walk.reachability.graph.OIDAt(parentPos)
+ if err != nil {
+ return nil, true, err
+ }
+
+ next = append(next, walkItem{id: parentOID, want: objecttype.TypeInvalid})
+ }
+
+ return next, true, nil
+}
diff --git a/reachability/walk_expand_objects.go b/reachability/walk_expand_objects.go
new file mode 100644
index 00000000..97cab79c
--- /dev/null
+++ b/reachability/walk_expand_objects.go
@@ -0,0 +1,83 @@
+package reachability
+
+import (
+ "fmt"
+
+ "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+func (walk *Walk) expandObjects(item walkItem) ([]walkItem, error) {
+ ty, err := walk.readHeaderType(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ if item.want != objecttype.TypeInvalid && ty != item.want {
+ return nil, &ErrObjectType{OID: item.id, Got: ty, Want: item.want}
+ }
+
+ switch ty {
+ case objecttype.TypeBlob:
+ return nil, nil
+ case objecttype.TypeCommit:
+ content, err := walk.readBytesContent(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := object.ParseCommit(content, item.id.Algorithm())
+ if err != nil {
+ return nil, err
+ }
+
+ next := make([]walkItem, 0, len(commit.Parents)+1)
+
+ next = append(next, walkItem{id: commit.Tree, want: objecttype.TypeTree})
+ for _, parent := range commit.Parents {
+ next = append(next, walkItem{id: parent, want: objecttype.TypeCommit})
+ }
+
+ return next, nil
+ case objecttype.TypeTree:
+ content, err := walk.readBytesContent(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ tree, err := object.ParseTree(content, item.id.Algorithm())
+ if err != nil {
+ return nil, err
+ }
+
+ next := make([]walkItem, 0, len(tree.Entries))
+ for _, entry := range tree.Entries {
+ switch entry.Mode {
+ case object.FileModeGitlink:
+ continue
+ case object.FileModeDir:
+ next = append(next, walkItem{id: entry.ID, want: objecttype.TypeTree})
+ case object.FileModeRegular, object.FileModeExecutable, object.FileModeSymlink:
+ next = append(next, walkItem{id: entry.ID, want: objecttype.TypeBlob})
+ }
+ }
+
+ return next, nil
+ case objecttype.TypeTag:
+ content, err := walk.readBytesContent(item.id)
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := object.ParseTag(content, item.id.Algorithm())
+ if err != nil {
+ return nil, err
+ }
+
+ return []walkItem{{id: tag.Target, want: tag.TargetType}}, nil
+ case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
+ return nil, &ErrObjectType{OID: item.id, Got: ty, Want: item.want}
+ }
+
+ return nil, fmt.Errorf("reachability: unreachable object type %d", ty)
+}
diff --git a/reachability/walk_item.go b/reachability/walk_item.go
new file mode 100644
index 00000000..7196cf1a
--- /dev/null
+++ b/reachability/walk_item.go
@@ -0,0 +1,11 @@
+package reachability
+
+import (
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+type walkItem struct {
+ id objectid.ObjectID
+ want objecttype.Type
+}
diff --git a/reachability/walk_seq.go b/reachability/walk_seq.go
new file mode 100644
index 00000000..ad3415d1
--- /dev/null
+++ b/reachability/walk_seq.go
@@ -0,0 +1,69 @@
+package reachability
+
+import (
+ "errors"
+ "iter"
+
+ "codeberg.org/lindenii/furgit/objectid"
+)
+
+// Seq returns the traversal sequence. It is single-use.
+func (walk *Walk) Seq() iter.Seq[objectid.ObjectID] {
+ if walk.seqUsed {
+ return func(yield func(objectid.ObjectID) bool) {
+ _ = yield
+
+ if walk.err == nil {
+ walk.err = errors.New("reachability: walk sequence already consumed")
+ }
+ }
+ }
+
+ walk.seqUsed = true
+
+ return func(yield func(objectid.ObjectID) bool) {
+ if walk.err != nil {
+ return
+ }
+
+ stack := walk.initialStack()
+
+ var err error
+
+ visited := make(map[objectid.ObjectID]struct{}, len(stack))
+ for len(stack) > 0 {
+ item := stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+
+ if containsOID(walk.haves, item.id) {
+ continue
+ }
+
+ if _, ok := visited[item.id]; ok {
+ continue
+ }
+
+ visited[item.id] = struct{}{}
+
+ var next []walkItem
+
+ next, err = walk.expand(item)
+ if err != nil {
+ walk.err = err
+
+ return
+ }
+
+ if !yield(item.id) {
+ return
+ }
+
+ stack = append(stack, next...)
+ }
+ }
+}
+
+// Err returns the terminal error, if any, once Seq has been consumed.
+func (walk *Walk) Err() error {
+ return walk.err
+}
diff --git a/reachability/walk_stack.go b/reachability/walk_stack.go
new file mode 100644
index 00000000..847ed1c0
--- /dev/null
+++ b/reachability/walk_stack.go
@@ -0,0 +1,16 @@
+package reachability
+
+import "codeberg.org/lindenii/furgit/objecttype"
+
+func (walk *Walk) initialStack() []walkItem {
+ if len(walk.wants) == 0 {
+ return nil
+ }
+
+ stack := make([]walkItem, 0, len(walk.wants))
+ for want := range walk.wants {
+ stack = append(stack, walkItem{id: want, want: objecttype.TypeInvalid})
+ }
+
+ return stack
+}
diff --git a/reachability/walk_verify.go b/reachability/walk_verify.go
new file mode 100644
index 00000000..46798f5a
--- /dev/null
+++ b/reachability/walk_verify.go
@@ -0,0 +1,27 @@
+package reachability
+
+import (
+ "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+func (walk *Walk) validateCommitObject(id objectid.ObjectID) error {
+ ty, err := walk.readHeaderType(id)
+ if err != nil {
+ return err
+ }
+
+ if ty != objecttype.TypeCommit {
+ return &ErrObjectType{OID: id, Got: ty, Want: objecttype.TypeCommit}
+ }
+
+ content, err := walk.readBytesContent(id)
+ if err != nil {
+ return err
+ }
+
+ _, err = object.ParseCommit(content, id.Algorithm())
+
+ return err
+}