aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-28 17:40:02 +0000
committerGravatar Runxi Yu2026-03-28 17:40:02 +0000
commit06583274fa64ad6677773bf4ca54f69226388203 (patch)
tree66354bf972d267bf7ee5135eb7e5b90c31f9fe4d
parentformat/commitgraph/read: Lifetime (diff)
signatureNo signature
commitquery: Make a reusable engine thingy
-rw-r--r--commitquery/ancestor.go4
-rw-r--r--commitquery/compare.go2
-rw-r--r--commitquery/context.go18
-rw-r--r--commitquery/generation.go4
-rw-r--r--commitquery/graph_pos.go6
-rw-r--r--commitquery/load.go2
-rw-r--r--commitquery/marks.go18
-rw-r--r--commitquery/merge_bases.go6
-rw-r--r--commitquery/node.go2
-rw-r--r--commitquery/oid.go10
-rw-r--r--commitquery/paint.go2
-rw-r--r--commitquery/parent.go4
-rw-r--r--commitquery/populate.go2
-rw-r--r--commitquery/priority_queue.go6
-rw-r--r--commitquery/queries.go23
-rw-r--r--commitquery/queries_acquire.go16
-rw-r--r--commitquery/queries_ancestor.go14
-rw-r--r--commitquery/queries_mergebase.go21
-rw-r--r--commitquery/queries_new.go25
-rw-r--r--commitquery/queries_release.go14
-rw-r--r--commitquery/reduce.go6
21 files changed, 161 insertions, 44 deletions
diff --git a/commitquery/ancestor.go b/commitquery/ancestor.go
index 4f182a95..6a43babb 100644
--- a/commitquery/ancestor.go
+++ b/commitquery/ancestor.go
@@ -6,7 +6,7 @@ import objectid "codeberg.org/lindenii/furgit/object/id"
// commit parent edges.
//
// Both inputs are peeled through annotated tags before commit traversal.
-func (query *Query) IsAncestor(ancestor, descendant objectid.ObjectID) (bool, error) {
+func (query *query) IsAncestor(ancestor, descendant objectid.ObjectID) (bool, error) {
ancestorIdx, err := query.resolveCommitish(ancestor)
if err != nil {
return false, err
@@ -20,7 +20,7 @@ func (query *Query) IsAncestor(ancestor, descendant objectid.ObjectID) (bool, er
return query.isAncestor(ancestorIdx, descendantIdx)
}
-func (query *Query) isAncestor(ancestor, descendant nodeIndex) (bool, error) {
+func (query *query) isAncestor(ancestor, descendant nodeIndex) (bool, error) {
if ancestor == descendant {
return true, nil
}
diff --git a/commitquery/compare.go b/commitquery/compare.go
index 21dc5eb9..7ae984dc 100644
--- a/commitquery/compare.go
+++ b/commitquery/compare.go
@@ -3,7 +3,7 @@ package commitquery
import objectid "codeberg.org/lindenii/furgit/object/id"
// Compare compares two internal nodes using merge-base queue ordering.
-func (query *Query) compare(left, right nodeIndex) int {
+func (query *query) compare(left, right nodeIndex) int {
leftGeneration := query.effectiveGeneration(left)
rightGeneration := query.effectiveGeneration(right)
diff --git a/commitquery/context.go b/commitquery/context.go
index 3e9ceee8..92705182 100644
--- a/commitquery/context.go
+++ b/commitquery/context.go
@@ -7,9 +7,7 @@ import (
objectstore "codeberg.org/lindenii/furgit/object/store"
)
-// Query owns the mutable node arena for commit-domain queries over one object
-// store.
-type Query struct {
+type query struct {
store objectstore.ReadingStore
graph *commitgraphread.Reader
@@ -22,13 +20,19 @@ type Query struct {
touched []nodeIndex
}
-// New builds one reusable commit query arena over one object store and optional
-// commit-graph reader.
-func New(store objectstore.ReadingStore, graph *commitgraphread.Reader) *Query {
- return &Query{
+func newQuery(store objectstore.ReadingStore, graph *commitgraphread.Reader) *query {
+ return &query{
store: store,
graph: graph,
byOID: make(map[objectid.ObjectID]nodeIndex),
byGraphPos: make(map[commitgraphread.Position]nodeIndex),
}
}
+
+func (query *query) resetForReuse() {
+ for _, idx := range query.touched {
+ query.nodes[idx].marks = 0
+ }
+
+ query.touched = query.touched[:0]
+}
diff --git a/commitquery/generation.go b/commitquery/generation.go
index 39ddd2e9..935da104 100644
--- a/commitquery/generation.go
+++ b/commitquery/generation.go
@@ -7,7 +7,7 @@ import (
)
// EffectiveGeneration returns one node's generation value.
-func (query *Query) effectiveGeneration(idx nodeIndex) uint64 {
+func (query *query) effectiveGeneration(idx nodeIndex) uint64 {
if !query.nodes[idx].hasGeneration {
return generationInfinity
}
@@ -19,7 +19,7 @@ const (
generationInfinity = uint64(math.MaxUint64)
)
-func compareByGeneration(query *Query) func(nodeIndex, nodeIndex) int {
+func compareByGeneration(query *query) func(nodeIndex, nodeIndex) int {
return func(left, right nodeIndex) int {
leftGeneration := query.effectiveGeneration(left)
rightGeneration := query.effectiveGeneration(right)
diff --git a/commitquery/graph_pos.go b/commitquery/graph_pos.go
index b5bc02d8..6b5118b0 100644
--- a/commitquery/graph_pos.go
+++ b/commitquery/graph_pos.go
@@ -3,7 +3,7 @@ package commitquery
import commitgraphread "codeberg.org/lindenii/furgit/format/commitgraph/read"
// resolveGraphPos resolves one commit-graph position to one internal query node.
-func (query *Query) resolveGraphPos(pos commitgraphread.Position) (nodeIndex, error) {
+func (query *query) resolveGraphPos(pos commitgraphread.Position) (nodeIndex, error) {
idx, ok := query.byGraphPos[pos]
if ok {
err := query.ensureLoaded(idx)
@@ -40,13 +40,13 @@ func (query *Query) resolveGraphPos(pos commitgraphread.Position) (nodeIndex, er
}
// loadByGraphPos populates one node from a commit-graph position.
-func (query *Query) loadByGraphPos(idx nodeIndex) error {
+func (query *query) loadByGraphPos(idx nodeIndex) error {
pos := query.nodes[idx].graphPos
return query.loadCommitAtGraphPos(idx, pos)
}
-func (query *Query) loadCommitAtGraphPos(idx nodeIndex, pos commitgraphread.Position) error {
+func (query *query) loadCommitAtGraphPos(idx nodeIndex, pos commitgraphread.Position) error {
commit, err := query.graph.CommitAt(pos)
if err != nil {
return err
diff --git a/commitquery/load.go b/commitquery/load.go
index 58ed7bf3..be39c7d9 100644
--- a/commitquery/load.go
+++ b/commitquery/load.go
@@ -1,7 +1,7 @@
package commitquery
// ensureLoaded completes one node's metadata load if it has not been loaded yet.
-func (query *Query) ensureLoaded(idx nodeIndex) error {
+func (query *query) ensureLoaded(idx nodeIndex) error {
if query.nodes[idx].loaded {
return nil
}
diff --git a/commitquery/marks.go b/commitquery/marks.go
index 401acb15..43ca9f44 100644
--- a/commitquery/marks.go
+++ b/commitquery/marks.go
@@ -1,22 +1,22 @@
package commitquery
// Marks returns the mark bits of one internal node.
-func (query *Query) marks(idx nodeIndex) markBits {
+func (query *query) marks(idx nodeIndex) markBits {
return query.nodes[idx].marks
}
// HasAnyMarks reports whether one internal node has any requested bit.
-func (query *Query) hasAnyMarks(idx nodeIndex, bits markBits) bool {
+func (query *query) hasAnyMarks(idx nodeIndex, bits markBits) bool {
return query.nodes[idx].marks&bits != 0
}
// HasAllMarks reports whether one internal node already has all requested bits.
-func (query *Query) hasAllMarks(idx nodeIndex, bits markBits) bool {
+func (query *query) hasAllMarks(idx nodeIndex, bits markBits) bool {
return query.nodes[idx].marks&bits == bits
}
// SetMarks ORs one set of mark bits into one internal node.
-func (query *Query) setMarks(idx nodeIndex, bits markBits) {
+func (query *query) setMarks(idx nodeIndex, bits markBits) {
newBits := bits &^ query.nodes[idx].marks
if newBits == 0 {
return
@@ -27,7 +27,7 @@ func (query *Query) setMarks(idx nodeIndex, bits markBits) {
}
// ClearMarks removes one set of mark bits from one internal node.
-func (query *Query) clearMarks(idx nodeIndex, bits markBits) {
+func (query *query) clearMarks(idx nodeIndex, bits markBits) {
if query.nodes[idx].marks&bits == 0 {
return
}
@@ -37,7 +37,7 @@ func (query *Query) clearMarks(idx nodeIndex, bits markBits) {
}
// BeginMarkPhase starts one tracked mark-mutation phase.
-func (query *Query) beginMarkPhase() {
+func (query *query) beginMarkPhase() {
for _, idx := range query.touched {
query.nodes[idx].marks = 0
}
@@ -55,13 +55,13 @@ func (query *Query) beginMarkPhase() {
// ClearTouchedMarks clears the provided bits from all nodes touched in the
// current mark phase.
-func (query *Query) clearTouchedMarks(bits markBits) {
+func (query *query) clearTouchedMarks(bits markBits) {
for _, idx := range query.touched {
query.nodes[idx].marks &^= bits
}
}
-func (query *Query) trackTouched(idx nodeIndex) {
+func (query *query) trackTouched(idx nodeIndex) {
if query.nodes[idx].touchedPhase == query.markPhase {
return
}
@@ -70,7 +70,7 @@ func (query *Query) trackTouched(idx nodeIndex) {
query.touched = append(query.touched, idx)
}
-func (query *Query) collectMarkedResults() []nodeIndex {
+func (query *query) collectMarkedResults() []nodeIndex {
out := make([]nodeIndex, 0, 4)
for _, idx := range query.touched {
diff --git a/commitquery/merge_bases.go b/commitquery/merge_bases.go
index 0e410709..281906f3 100644
--- a/commitquery/merge_bases.go
+++ b/commitquery/merge_bases.go
@@ -9,7 +9,7 @@ import (
// MergeBases reports all merge bases in Git's merge-base --all order.
//
// Both inputs are peeled through annotated tags before commit traversal.
-func (query *Query) MergeBases(left, right objectid.ObjectID) ([]objectid.ObjectID, error) {
+func (query *query) MergeBases(left, right objectid.ObjectID) ([]objectid.ObjectID, error) {
leftIdx, err := query.resolveCommitish(left)
if err != nil {
return nil, err
@@ -45,7 +45,7 @@ func (query *Query) MergeBases(left, right objectid.ObjectID) ([]objectid.Object
}
// MergeBase reports one merge base between left and right, if any.
-func (query *Query) MergeBase(left, right objectid.ObjectID) (objectid.ObjectID, bool, error) {
+func (query *query) MergeBase(left, right objectid.ObjectID) (objectid.ObjectID, bool, error) {
bases, err := query.MergeBases(left, right)
if err != nil {
return objectid.ObjectID{}, false, err
@@ -58,7 +58,7 @@ func (query *Query) MergeBase(left, right objectid.ObjectID) (objectid.ObjectID,
return bases[0], true, nil
}
-func (query *Query) mergeBases(left, right nodeIndex) ([]nodeIndex, error) {
+func (query *query) mergeBases(left, right nodeIndex) ([]nodeIndex, error) {
if left == right {
return []nodeIndex{left}, nil
}
diff --git a/commitquery/node.go b/commitquery/node.go
index c0f25c55..d5f1d3cd 100644
--- a/commitquery/node.go
+++ b/commitquery/node.go
@@ -28,7 +28,7 @@ type node struct {
}
// newNode allocates one empty internal node.
-func (query *Query) newNode(id objectid.ObjectID) nodeIndex {
+func (query *query) newNode(id objectid.ObjectID) nodeIndex {
count := len(query.nodes)
idx := nodeIndex(count)
diff --git a/commitquery/oid.go b/commitquery/oid.go
index 22d41a82..fd524dfb 100644
--- a/commitquery/oid.go
+++ b/commitquery/oid.go
@@ -12,15 +12,15 @@ import (
objecttype "codeberg.org/lindenii/furgit/object/type"
)
-func (query *Query) id(idx nodeIndex) objectid.ObjectID {
+func (query *query) id(idx nodeIndex) objectid.ObjectID {
return query.nodes[idx].id
}
-func (query *Query) commitTime(idx nodeIndex) int64 {
+func (query *query) commitTime(idx nodeIndex) int64 {
return query.nodes[idx].commitTime
}
-func (query *Query) resolveOID(id objectid.ObjectID) (nodeIndex, error) {
+func (query *query) resolveOID(id objectid.ObjectID) (nodeIndex, error) {
idx, ok := query.byOID[id]
if ok {
err := query.ensureLoaded(idx)
@@ -44,7 +44,7 @@ func (query *Query) resolveOID(id objectid.ObjectID) (nodeIndex, error) {
return idx, nil
}
-func (query *Query) resolveCommitish(id objectid.ObjectID) (nodeIndex, error) {
+func (query *query) resolveCommitish(id objectid.ObjectID) (nodeIndex, error) {
commitID, err := peel.ToCommit(query.store, id)
if err != nil {
return 0, err
@@ -54,7 +54,7 @@ func (query *Query) resolveCommitish(id objectid.ObjectID) (nodeIndex, error) {
}
// loadByOID populates one node from an object ID.
-func (query *Query) loadByOID(idx nodeIndex) error {
+func (query *query) loadByOID(idx nodeIndex) error {
id := query.nodes[idx].id
if query.graph != nil {
diff --git a/commitquery/paint.go b/commitquery/paint.go
index 90b38b10..6bb1f489 100644
--- a/commitquery/paint.go
+++ b/commitquery/paint.go
@@ -1,6 +1,6 @@
package commitquery
-func (query *Query) paintDownToCommon(left nodeIndex, rights []nodeIndex, minGeneration uint64) error {
+func (query *query) paintDownToCommon(left nodeIndex, rights []nodeIndex, minGeneration uint64) error {
query.beginMarkPhase()
query.setMarks(left, markLeft)
diff --git a/commitquery/parent.go b/commitquery/parent.go
index e7703f77..fe0f4fba 100644
--- a/commitquery/parent.go
+++ b/commitquery/parent.go
@@ -13,12 +13,12 @@ type parentRef struct {
}
// Parents returns resolved parent node indices for one internal node.
-func (query *Query) parents(idx nodeIndex) []nodeIndex {
+func (query *query) parents(idx nodeIndex) []nodeIndex {
return query.nodes[idx].parents
}
// resolveParent resolves one parent descriptor to one internal node.
-func (query *Query) resolveParent(parent parentRef) (nodeIndex, error) {
+func (query *query) resolveParent(parent parentRef) (nodeIndex, error) {
if parent.HasGraphPos {
return query.resolveGraphPos(parent.GraphPos)
}
diff --git a/commitquery/populate.go b/commitquery/populate.go
index cc71955f..26fb5629 100644
--- a/commitquery/populate.go
+++ b/commitquery/populate.go
@@ -3,7 +3,7 @@ package commitquery
import "fmt"
// populateNode fills one node's metadata and resolves its parents.
-func (query *Query) populateNode(idx nodeIndex, commit commitData) error {
+func (query *query) populateNode(idx nodeIndex, commit commitData) error {
if query.nodes[idx].loaded {
if query.nodes[idx].id != commit.ID {
return fmt.Errorf("commitquery: node identity mismatch: have %s, got %s", query.nodes[idx].id, commit.ID)
diff --git a/commitquery/priority_queue.go b/commitquery/priority_queue.go
index 0d948c64..8651ea0b 100644
--- a/commitquery/priority_queue.go
+++ b/commitquery/priority_queue.go
@@ -4,12 +4,12 @@ import "container/heap"
// priorityQueue orders internal nodes using one query context's comparator.
type priorityQueue struct {
- query *Query
+ query *query
items []nodeIndex
}
// newPriorityQueue builds one empty priority queue over one query context.
-func newPriorityQueue(query *Query) *priorityQueue {
+func newPriorityQueue(query *query) *priorityQueue {
queue := &priorityQueue{query: query}
heap.Init(queue)
@@ -67,7 +67,7 @@ func (queue *priorityQueue) PopNode() nodeIndex {
return idx
}
-func (query *Query) queueHasNonStale(queue *priorityQueue) bool {
+func (query *query) queueHasNonStale(queue *priorityQueue) bool {
// TODO
for _, idx := range queue.items {
if !query.hasAnyMarks(idx, markStale) {
diff --git a/commitquery/queries.go b/commitquery/queries.go
new file mode 100644
index 00000000..dec8606f
--- /dev/null
+++ b/commitquery/queries.go
@@ -0,0 +1,23 @@
+package commitquery
+
+import (
+ "sync"
+
+ commitgraphread "codeberg.org/lindenii/furgit/format/commitgraph/read"
+ objectstore "codeberg.org/lindenii/furgit/object/store"
+)
+
+// Queries provides commit-domain queries over one object store
+// and optional commit-graph reader.
+//
+// Queries reuses internal mutable query workers across operations.
+//
+// Labels: MT-Safe.
+type Queries struct {
+ store objectstore.ReadingStore
+ graph *commitgraphread.Reader
+
+ mu sync.Mutex
+ idle []*query
+ maxIdle int
+}
diff --git a/commitquery/queries_acquire.go b/commitquery/queries_acquire.go
new file mode 100644
index 00000000..ac7b765c
--- /dev/null
+++ b/commitquery/queries_acquire.go
@@ -0,0 +1,16 @@
+package commitquery
+
+func (queries *Queries) acquire() *query {
+ queries.mu.Lock()
+ defer queries.mu.Unlock()
+
+ count := len(queries.idle)
+ if count == 0 {
+ return newQuery(queries.store, queries.graph)
+ }
+
+ q := queries.idle[count-1]
+ queries.idle = queries.idle[:count-1]
+
+ return q
+}
diff --git a/commitquery/queries_ancestor.go b/commitquery/queries_ancestor.go
new file mode 100644
index 00000000..e2c955c6
--- /dev/null
+++ b/commitquery/queries_ancestor.go
@@ -0,0 +1,14 @@
+package commitquery
+
+import objectid "codeberg.org/lindenii/furgit/object/id"
+
+// IsAncestor reports whether ancestor is reachable from descendant through
+// commit parent edges.
+//
+// Both inputs are peeled through annotated tags before commit traversal.
+func (queries *Queries) IsAncestor(ancestor, descendant objectid.ObjectID) (bool, error) {
+ query := queries.acquire()
+ defer queries.release(query)
+
+ return query.IsAncestor(ancestor, descendant)
+}
diff --git a/commitquery/queries_mergebase.go b/commitquery/queries_mergebase.go
new file mode 100644
index 00000000..ff0aafb9
--- /dev/null
+++ b/commitquery/queries_mergebase.go
@@ -0,0 +1,21 @@
+package commitquery
+
+import objectid "codeberg.org/lindenii/furgit/object/id"
+
+// MergeBases reports all merge bases in Git's merge-base --all order.
+//
+// Both inputs are peeled through annotated tags before commit traversal.
+func (queries *Queries) MergeBases(left, right objectid.ObjectID) ([]objectid.ObjectID, error) {
+ query := queries.acquire()
+ defer queries.release(query)
+
+ return query.MergeBases(left, right)
+}
+
+// MergeBase reports one merge base between left and right, if any.
+func (queries *Queries) MergeBase(left, right objectid.ObjectID) (objectid.ObjectID, bool, error) {
+ query := queries.acquire()
+ defer queries.release(query)
+
+ return query.MergeBase(left, right)
+}
diff --git a/commitquery/queries_new.go b/commitquery/queries_new.go
new file mode 100644
index 00000000..b43084b4
--- /dev/null
+++ b/commitquery/queries_new.go
@@ -0,0 +1,25 @@
+package commitquery
+
+import (
+ "runtime"
+
+ commitgraphread "codeberg.org/lindenii/furgit/format/commitgraph/read"
+ objectstore "codeberg.org/lindenii/furgit/object/store"
+)
+
+// New builds one concurrent-safe commit query service over one object store
+// and optional commit-graph reader.
+//
+// Labels: Deps-Borrowed.
+func New(store objectstore.ReadingStore, graph *commitgraphread.Reader) *Queries {
+ maxIdle := runtime.GOMAXPROCS(0)
+ if maxIdle < 1 {
+ maxIdle = 1
+ }
+
+ return &Queries{
+ store: store,
+ graph: graph,
+ maxIdle: maxIdle,
+ }
+}
diff --git a/commitquery/queries_release.go b/commitquery/queries_release.go
new file mode 100644
index 00000000..791b2f03
--- /dev/null
+++ b/commitquery/queries_release.go
@@ -0,0 +1,14 @@
+package commitquery
+
+func (queries *Queries) release(q *query) {
+ q.resetForReuse()
+
+ queries.mu.Lock()
+ defer queries.mu.Unlock()
+
+ if len(queries.idle) >= queries.maxIdle {
+ return
+ }
+
+ queries.idle = append(queries.idle, q)
+}
diff --git a/commitquery/reduce.go b/commitquery/reduce.go
index ac85d619..f8a86f69 100644
--- a/commitquery/reduce.go
+++ b/commitquery/reduce.go
@@ -5,7 +5,7 @@ import (
)
// removeRedundant removes redundant merge-base candidates.
-func removeRedundant(query *Query, candidates []nodeIndex) ([]nodeIndex, error) {
+func removeRedundant(query *query, candidates []nodeIndex) ([]nodeIndex, error) {
for _, idx := range candidates {
if query.effectiveGeneration(idx) != generationInfinity {
return removeRedundantWithGen(query, candidates), nil
@@ -15,7 +15,7 @@ func removeRedundant(query *Query, candidates []nodeIndex) ([]nodeIndex, error)
return removeRedundantNoGen(query, candidates)
}
-func removeRedundantNoGen(query *Query, candidates []nodeIndex) ([]nodeIndex, error) {
+func removeRedundantNoGen(query *query, candidates []nodeIndex) ([]nodeIndex, error) {
redundant := make([]bool, len(candidates))
work := make([]nodeIndex, 0, len(candidates)-1)
filledIndex := make([]int, 0, len(candidates)-1)
@@ -72,7 +72,7 @@ func removeRedundantNoGen(query *Query, candidates []nodeIndex) ([]nodeIndex, er
return out, nil
}
-func removeRedundantWithGen(query *Query, candidates []nodeIndex) []nodeIndex {
+func removeRedundantWithGen(query *query, candidates []nodeIndex) []nodeIndex {
sorted := append([]nodeIndex(nil), candidates...)
slices.SortFunc(sorted, compareByGeneration(query))