diff options
| author | 2026-03-11 20:41:32 +0800 | |
|---|---|---|
| committer | 2026-03-11 20:41:32 +0800 | |
| commit | 040b572d95e4ca27e1ada6113c405b8a1eb4a669 (patch) | |
| tree | 68d826f4d91144105802c9d1c67175ba9b314e29 /internal | |
| parent | research: Maybe drop mmap in packfile_bloom (diff) | |
| signature | No signature | |
commitquery: Merge from ancestor and mergebases
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/commitquery/ancestor.go | 30 | ||||
| -rw-r--r-- | internal/commitquery/bits.go | 14 | ||||
| -rw-r--r-- | internal/commitquery/commit.go | 17 | ||||
| -rw-r--r-- | internal/commitquery/compare.go | 25 | ||||
| -rw-r--r-- | internal/commitquery/context.go | 32 | ||||
| -rw-r--r-- | internal/commitquery/errors.go | 5 | ||||
| -rw-r--r-- | internal/commitquery/generation.go | 43 | ||||
| -rw-r--r-- | internal/commitquery/graph_pos.go | 107 | ||||
| -rw-r--r-- | internal/commitquery/load.go | 14 | ||||
| -rw-r--r-- | internal/commitquery/marks.go | 67 | ||||
| -rw-r--r-- | internal/commitquery/merge_bases.go | 116 | ||||
| -rw-r--r-- | internal/commitquery/node.go | 39 | ||||
| -rw-r--r-- | internal/commitquery/oid.go | 94 | ||||
| -rw-r--r-- | internal/commitquery/parent.go | 27 | ||||
| -rw-r--r-- | internal/commitquery/populate.go | 42 | ||||
| -rw-r--r-- | internal/commitquery/priority_queue.go | 68 | ||||
| -rw-r--r-- | internal/commitquery/reduce.go | 166 |
17 files changed, 0 insertions, 906 deletions
diff --git a/internal/commitquery/ancestor.go b/internal/commitquery/ancestor.go deleted file mode 100644 index d050ce08..00000000 --- a/internal/commitquery/ancestor.go +++ /dev/null @@ -1,30 +0,0 @@ -package commitquery - -// IsAncestor reports whether ancestor is reachable from descendant through -// commit parent edges. -func IsAncestor(ctx *Context, ancestor, descendant NodeIndex) (bool, error) { - if ancestor == descendant { - return true, nil - } - - ancestorGeneration := ctx.EffectiveGeneration(ancestor) - descendantGeneration := ctx.EffectiveGeneration(descendant) - - if ancestorGeneration != generationInfinity && - descendantGeneration != generationInfinity && - ancestorGeneration > descendantGeneration { - return false, nil - } - - minGeneration := uint64(0) - if ancestorGeneration != generationInfinity { - minGeneration = ancestorGeneration - } - - err := paintDownToCommon(ctx, ancestor, []NodeIndex{descendant}, minGeneration) - if err != nil { - return false, err - } - - return ctx.HasAnyMarks(ancestor, markRight), nil -} diff --git a/internal/commitquery/bits.go b/internal/commitquery/bits.go deleted file mode 100644 index 36ffff29..00000000 --- a/internal/commitquery/bits.go +++ /dev/null @@ -1,14 +0,0 @@ -package commitquery - -type markBits uint8 - -const ( - markLeft markBits = 1 << iota - markRight - markStale - markResult -) - -const ( - allMarks = markLeft | markRight | markStale | markResult -) diff --git a/internal/commitquery/commit.go b/internal/commitquery/commit.go deleted file mode 100644 index fccd52b2..00000000 --- a/internal/commitquery/commit.go +++ /dev/null @@ -1,17 +0,0 @@ -package commitquery - -import ( - commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - "codeberg.org/lindenii/furgit/objectid" -) - -// Commit stores the metadata needed by commit-domain queries. -type Commit struct { - ID objectid.ObjectID - Parents []Parent - CommitTime int64 - Generation uint64 - HasGeneration bool - GraphPos commitgraphread.Position - HasGraphPos bool -} diff --git a/internal/commitquery/compare.go b/internal/commitquery/compare.go deleted file mode 100644 index 748ef712..00000000 --- a/internal/commitquery/compare.go +++ /dev/null @@ -1,25 +0,0 @@ -package commitquery - -import "codeberg.org/lindenii/furgit/objectid" - -// Compare compares two internal nodes using merge-base queue ordering. -func (ctx *Context) Compare(left, right NodeIndex) int { - leftGeneration := ctx.EffectiveGeneration(left) - rightGeneration := ctx.EffectiveGeneration(right) - - switch { - case leftGeneration < rightGeneration: - return -1 - case leftGeneration > rightGeneration: - return 1 - } - - switch { - case ctx.nodes[left].commitTime < ctx.nodes[right].commitTime: - return -1 - case ctx.nodes[left].commitTime > ctx.nodes[right].commitTime: - return 1 - } - - return objectid.Compare(ctx.nodes[left].id, ctx.nodes[right].id) -} diff --git a/internal/commitquery/context.go b/internal/commitquery/context.go deleted file mode 100644 index bafbb5c4..00000000 --- a/internal/commitquery/context.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package commitquery provides private commit-domain query routines. -package commitquery - -import ( - commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - "codeberg.org/lindenii/furgit/objectid" - "codeberg.org/lindenii/furgit/objectstore" -) - -// Context owns the mutable node arena for one commit query. -type Context struct { - store objectstore.Store - graph *commitgraphread.Reader - - nodes []node - - byOID map[objectid.ObjectID]NodeIndex - byGraphPos map[commitgraphread.Position]NodeIndex - - markPhase uint32 - touched []NodeIndex -} - -// NewContext builds one empty query context over one object store and optional commit-graph reader. -func NewContext(store objectstore.Store, graph *commitgraphread.Reader) *Context { - return &Context{ - store: store, - graph: graph, - byOID: make(map[objectid.ObjectID]NodeIndex), - byGraphPos: make(map[commitgraphread.Position]NodeIndex), - } -} diff --git a/internal/commitquery/errors.go b/internal/commitquery/errors.go deleted file mode 100644 index e99011d0..00000000 --- a/internal/commitquery/errors.go +++ /dev/null @@ -1,5 +0,0 @@ -package commitquery - -import "errors" - -var errBadGenerationOrder = errors.New("commitquery: priority queue violated generation ordering") diff --git a/internal/commitquery/generation.go b/internal/commitquery/generation.go deleted file mode 100644 index c5edcd9f..00000000 --- a/internal/commitquery/generation.go +++ /dev/null @@ -1,43 +0,0 @@ -package commitquery - -import ( - "math" - - "codeberg.org/lindenii/furgit/objectid" -) - -// EffectiveGeneration returns one node's generation value. -func (ctx *Context) EffectiveGeneration(idx NodeIndex) uint64 { - if !ctx.nodes[idx].hasGeneration { - return generationInfinity - } - - return ctx.nodes[idx].generation -} - -const ( - generationInfinity = uint64(math.MaxUint64) -) - -func compareByGeneration(ctx *Context) func(NodeIndex, NodeIndex) int { - return func(left, right NodeIndex) int { - leftGeneration := ctx.EffectiveGeneration(left) - rightGeneration := ctx.EffectiveGeneration(right) - - switch { - case leftGeneration < rightGeneration: - return -1 - case leftGeneration > rightGeneration: - return 1 - } - - switch { - case ctx.nodes[left].commitTime < ctx.nodes[right].commitTime: - return -1 - case ctx.nodes[left].commitTime > ctx.nodes[right].commitTime: - return 1 - } - - return objectid.Compare(ctx.nodes[left].id, ctx.nodes[right].id) - } -} diff --git a/internal/commitquery/graph_pos.go b/internal/commitquery/graph_pos.go deleted file mode 100644 index 437f86e6..00000000 --- a/internal/commitquery/graph_pos.go +++ /dev/null @@ -1,107 +0,0 @@ -package commitquery - -import commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - -// ResolveGraphPos resolves one commit-graph position to one internal query node. -func (ctx *Context) ResolveGraphPos(pos commitgraphread.Position) (NodeIndex, error) { - idx, ok := ctx.byGraphPos[pos] - if ok { - err := ctx.ensureLoaded(idx) - if err != nil { - return 0, err - } - - return idx, nil - } - - commit, err := ctx.graph.CommitAt(pos) - if err != nil { - return 0, err - } - - idx, ok = ctx.byOID[commit.OID] - if !ok { - idx = ctx.newNode(commit.OID) - ctx.byOID[commit.OID] = idx - } - - ctx.byGraphPos[pos] = idx - ctx.nodes[idx].graphPos = pos - ctx.nodes[idx].hasGraphPos = true - - err = ctx.loadCommitAtGraphPos(idx, pos) - if err != nil { - delete(ctx.byGraphPos, pos) - - return 0, err - } - - return idx, nil -} - -// loadByGraphPos populates one node from a commit-graph position. -func (ctx *Context) loadByGraphPos(idx NodeIndex) error { - pos := ctx.nodes[idx].graphPos - - return ctx.loadCommitAtGraphPos(idx, pos) -} - -func (ctx *Context) loadCommitAtGraphPos(idx NodeIndex, pos commitgraphread.Position) error { - commit, err := ctx.graph.CommitAt(pos) - if err != nil { - return err - } - - parents := make([]Parent, 0, 2+len(commit.ExtraParents)) - - if commit.Parent1.Valid { - parentOID, err := ctx.graph.OIDAt(commit.Parent1.Pos) - if err != nil { - return err - } - - parents = append(parents, Parent{ - ID: parentOID, - GraphPos: commit.Parent1.Pos, - HasGraphPos: true, - }) - } - - if commit.Parent2.Valid { - parentOID, err := ctx.graph.OIDAt(commit.Parent2.Pos) - if err != nil { - return err - } - - parents = append(parents, Parent{ - ID: parentOID, - GraphPos: commit.Parent2.Pos, - HasGraphPos: true, - }) - } - - for _, parentPos := range commit.ExtraParents { - parentOID, err := ctx.graph.OIDAt(parentPos) - if err != nil { - return err - } - - parents = append(parents, Parent{ - ID: parentOID, - GraphPos: parentPos, - HasGraphPos: true, - }) - } - - data := Commit{ - ID: commit.OID, - Parents: parents, - CommitTime: commit.CommitTimeUnix, - Generation: commit.GenerationV2, - HasGeneration: commit.GenerationV2 != 0, - GraphPos: pos, - HasGraphPos: true, - } - - return ctx.populateNode(idx, data) -} diff --git a/internal/commitquery/load.go b/internal/commitquery/load.go deleted file mode 100644 index b795f7d9..00000000 --- a/internal/commitquery/load.go +++ /dev/null @@ -1,14 +0,0 @@ -package commitquery - -// ensureLoaded completes one node's metadata load if it has not been loaded yet. -func (ctx *Context) ensureLoaded(idx NodeIndex) error { - if ctx.nodes[idx].loaded { - return nil - } - - if ctx.nodes[idx].hasGraphPos { - return ctx.loadByGraphPos(idx) - } - - return ctx.loadByOID(idx) -} diff --git a/internal/commitquery/marks.go b/internal/commitquery/marks.go deleted file mode 100644 index f88fdf25..00000000 --- a/internal/commitquery/marks.go +++ /dev/null @@ -1,67 +0,0 @@ -package commitquery - -// Marks returns the mark bits of one internal node. -func (ctx *Context) Marks(idx NodeIndex) markBits { - return ctx.nodes[idx].marks -} - -// HasAnyMarks reports whether one internal node has any requested bit. -func (ctx *Context) HasAnyMarks(idx NodeIndex, bits markBits) bool { - return ctx.nodes[idx].marks&bits != 0 -} - -// HasAllMarks reports whether one internal node already has all requested bits. -func (ctx *Context) HasAllMarks(idx NodeIndex, bits markBits) bool { - return ctx.nodes[idx].marks&bits == bits -} - -// SetMarks ORs one set of mark bits into one internal node. -func (ctx *Context) SetMarks(idx NodeIndex, bits markBits) { - newBits := bits &^ ctx.nodes[idx].marks - if newBits == 0 { - return - } - - ctx.trackTouched(idx) - ctx.nodes[idx].marks |= bits -} - -// ClearMarks removes one set of mark bits from one internal node. -func (ctx *Context) ClearMarks(idx NodeIndex, bits markBits) { - if ctx.nodes[idx].marks&bits == 0 { - return - } - - ctx.trackTouched(idx) - ctx.nodes[idx].marks &^= bits -} - -// BeginMarkPhase starts one tracked mark-mutation phase. -func (ctx *Context) BeginMarkPhase() { - ctx.markPhase++ - if ctx.markPhase == 0 { - ctx.markPhase++ - for i := range ctx.nodes { - ctx.nodes[i].touchedPhase = 0 - } - } - - ctx.touched = ctx.touched[:0] -} - -// ClearTouchedMarks clears the provided bits from all nodes touched in the -// current mark phase. -func (ctx *Context) ClearTouchedMarks(bits markBits) { - for _, idx := range ctx.touched { - ctx.nodes[idx].marks &^= bits - } -} - -func (ctx *Context) trackTouched(idx NodeIndex) { - if ctx.nodes[idx].touchedPhase == ctx.markPhase { - return - } - - ctx.nodes[idx].touchedPhase = ctx.markPhase - ctx.touched = append(ctx.touched, idx) -} diff --git a/internal/commitquery/merge_bases.go b/internal/commitquery/merge_bases.go deleted file mode 100644 index 62ab9bd6..00000000 --- a/internal/commitquery/merge_bases.go +++ /dev/null @@ -1,116 +0,0 @@ -package commitquery - -import "slices" - -// MergeBases computes fully reduced merge bases using one query context. -func MergeBases(ctx *Context, left, right NodeIndex) ([]NodeIndex, error) { - if left == right { - return []NodeIndex{left}, nil - } - - err := paintDownToCommon(ctx, left, []NodeIndex{right}, 0) - if err != nil { - return nil, err - } - - candidates := collectMarkedResults(ctx) - - if len(candidates) <= 1 { - slices.SortFunc(candidates, ctx.Compare) - - return candidates, nil - } - - ctx.ClearTouchedMarks(allMarks) - - reduced, err := removeRedundant(ctx, candidates) - if err != nil { - return nil, err - } - - slices.SortFunc(reduced, ctx.Compare) - - return reduced, nil -} - -func paintDownToCommon(ctx *Context, left NodeIndex, rights []NodeIndex, minGeneration uint64) error { - ctx.BeginMarkPhase() - - ctx.SetMarks(left, markLeft) - - if len(rights) == 0 { - ctx.SetMarks(left, markResult) - - return nil - } - - queue := newPriorityQueue(ctx) - queue.PushNode(left) - - for _, right := range rights { - ctx.SetMarks(right, markRight) - queue.PushNode(right) - } - - lastGeneration := generationInfinity - - for queueHasNonStale(ctx, queue) { - idx := queue.PopNode() - - generation := ctx.EffectiveGeneration(idx) - if generation > lastGeneration { - return errBadGenerationOrder - } - - lastGeneration = generation - if generation < minGeneration { - break - } - - flags := ctx.Marks(idx) & (markLeft | markRight | markStale) - if flags == (markLeft | markRight) { - ctx.SetMarks(idx, markResult) - - flags |= markStale - } - - for _, parent := range ctx.Parents(idx) { - if ctx.HasAllMarks(parent, flags) { - continue - } - - ctx.SetMarks(parent, flags) - queue.PushNode(parent) - } - } - - return nil -} - -func queueHasNonStale(ctx *Context, queue *priorityQueue) bool { - for _, idx := range queue.items { - if !ctx.HasAnyMarks(idx, markStale) { - return true - } - } - - return false -} - -func collectMarkedResults(ctx *Context) []NodeIndex { - out := make([]NodeIndex, 0, 4) - - for _, idx := range ctx.touched { - if !ctx.HasAnyMarks(idx, markResult) { - continue - } - - if ctx.HasAnyMarks(idx, markStale) { - continue - } - - out = append(out, idx) - } - - return out -} diff --git a/internal/commitquery/node.go b/internal/commitquery/node.go deleted file mode 100644 index f4979bfe..00000000 --- a/internal/commitquery/node.go +++ /dev/null @@ -1,39 +0,0 @@ -package commitquery - -import ( - commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - "codeberg.org/lindenii/furgit/objectid" -) - -// NodeIndex identifies one internal query node. -type NodeIndex int - -// node stores one mutable commit traversal node. -type node struct { - id objectid.ObjectID - - parents []NodeIndex - - commitTime int64 - generation uint64 - - hasGeneration bool - hasGraphPos bool - loaded bool - - graphPos commitgraphread.Position - marks markBits - - touchedPhase uint32 -} - -// newNode allocates one empty internal node. -func (ctx *Context) newNode(id objectid.ObjectID) NodeIndex { - count := len(ctx.nodes) - - idx := NodeIndex(count) - - ctx.nodes = append(ctx.nodes, node{id: id}) - - return idx -} diff --git a/internal/commitquery/oid.go b/internal/commitquery/oid.go deleted file mode 100644 index 927c0220..00000000 --- a/internal/commitquery/oid.go +++ /dev/null @@ -1,94 +0,0 @@ -package commitquery - -import ( - stderrors "errors" - - commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - giterrors "codeberg.org/lindenii/furgit/errors" - "codeberg.org/lindenii/furgit/object" - "codeberg.org/lindenii/furgit/objectid" - "codeberg.org/lindenii/furgit/objectstore" - "codeberg.org/lindenii/furgit/objecttype" -) - -// ID returns the canonical object ID of one internal node. -func (ctx *Context) ID(idx NodeIndex) objectid.ObjectID { - return ctx.nodes[idx].id -} - -// CommitTime returns the committer timestamp used for one internal node. -func (ctx *Context) CommitTime(idx NodeIndex) int64 { - return ctx.nodes[idx].commitTime -} - -// ResolveOID resolves one commit object ID to one internal query node. -func (ctx *Context) ResolveOID(id objectid.ObjectID) (NodeIndex, error) { - idx, ok := ctx.byOID[id] - if ok { - err := ctx.ensureLoaded(idx) - if err != nil { - return 0, err - } - - return idx, nil - } - - idx = ctx.newNode(id) - ctx.byOID[id] = idx - - err := ctx.loadByOID(idx) - if err != nil { - delete(ctx.byOID, id) - - return 0, err - } - - return idx, nil -} - -// loadByOID populates one node from an object ID. -func (ctx *Context) loadByOID(idx NodeIndex) error { - id := ctx.nodes[idx].id - - if ctx.graph != nil { - pos, err := ctx.graph.Lookup(id) - if err != nil { - if _, ok := stderrors.AsType[*commitgraphread.NotFoundError](err); !ok { - return err - } - } else { - return ctx.loadCommitAtGraphPos(idx, pos) - } - } - - ty, content, err := ctx.store.ReadBytesContent(id) - if err != nil { - if stderrors.Is(err, objectstore.ErrObjectNotFound) { - return &giterrors.ObjectMissingError{OID: id} - } - - return err - } - - if ty != objecttype.TypeCommit { - return &giterrors.ObjectTypeError{OID: id, Got: ty, Want: objecttype.TypeCommit} - } - - commitObj, err := object.ParseCommit(content, id.Algorithm()) - if err != nil { - return err - } - - parents := make([]Parent, 0, len(commitObj.Parents)) - for _, parentID := range commitObj.Parents { - parents = append(parents, Parent{ID: parentID}) - } - - commit := Commit{ - ID: id, - Parents: parents, - CommitTime: commitObj.Committer.WhenUnix, - } - - return ctx.populateNode(idx, commit) -} diff --git a/internal/commitquery/parent.go b/internal/commitquery/parent.go deleted file mode 100644 index 809a1fd5..00000000 --- a/internal/commitquery/parent.go +++ /dev/null @@ -1,27 +0,0 @@ -package commitquery - -import ( - commitgraphread "codeberg.org/lindenii/furgit/commitgraph/read" - "codeberg.org/lindenii/furgit/objectid" -) - -// Parent references one commit parent. -type Parent struct { - ID objectid.ObjectID - GraphPos commitgraphread.Position - HasGraphPos bool -} - -// Parents returns resolved parent node indices for one internal node. -func (ctx *Context) Parents(idx NodeIndex) []NodeIndex { - return ctx.nodes[idx].parents -} - -// resolveParent resolves one parent descriptor to one internal node. -func (ctx *Context) resolveParent(parent Parent) (NodeIndex, error) { - if parent.HasGraphPos { - return ctx.ResolveGraphPos(parent.GraphPos) - } - - return ctx.ResolveOID(parent.ID) -} diff --git a/internal/commitquery/populate.go b/internal/commitquery/populate.go deleted file mode 100644 index 87d65bf8..00000000 --- a/internal/commitquery/populate.go +++ /dev/null @@ -1,42 +0,0 @@ -package commitquery - -import "fmt" - -// populateNode fills one node's metadata and resolves its parents. -func (ctx *Context) populateNode(idx NodeIndex, commit Commit) error { - if ctx.nodes[idx].loaded { - if ctx.nodes[idx].id != commit.ID { - return fmt.Errorf("commitquery: node identity mismatch: have %s, got %s", ctx.nodes[idx].id, commit.ID) - } - - return nil - } - - ctx.nodes[idx].id = commit.ID - ctx.nodes[idx].commitTime = commit.CommitTime - ctx.nodes[idx].generation = commit.Generation - ctx.nodes[idx].hasGeneration = commit.HasGeneration - - if commit.HasGraphPos { - ctx.nodes[idx].graphPos = commit.GraphPos - ctx.nodes[idx].hasGraphPos = true - ctx.byGraphPos[commit.GraphPos] = idx - } - - ctx.nodes[idx].loaded = true - ctx.nodes[idx].parents = ctx.nodes[idx].parents[:0] - - for _, parent := range commit.Parents { - parentIdx, err := ctx.resolveParent(parent) - if err != nil { - ctx.nodes[idx].loaded = false - ctx.nodes[idx].parents = nil - - return err - } - - ctx.nodes[idx].parents = append(ctx.nodes[idx].parents, parentIdx) - } - - return nil -} diff --git a/internal/commitquery/priority_queue.go b/internal/commitquery/priority_queue.go deleted file mode 100644 index d6dbf54f..00000000 --- a/internal/commitquery/priority_queue.go +++ /dev/null @@ -1,68 +0,0 @@ -package commitquery - -import "container/heap" - -// priorityQueue orders internal nodes using one query context's comparator. -type priorityQueue struct { - ctx *Context - items []NodeIndex -} - -// newPriorityQueue builds one empty priority queue over one query context. -func newPriorityQueue(ctx *Context) *priorityQueue { - queue := &priorityQueue{ctx: ctx} - heap.Init(queue) - - return queue -} - -// Len reports the number of queued items. -func (queue *priorityQueue) Len() int { - return len(queue.items) -} - -// Less reports whether one heap slot sorts ahead of another. -func (queue *priorityQueue) Less(left, right int) bool { - return queue.ctx.Compare(queue.items[left], queue.items[right]) > 0 -} - -// Swap exchanges two heap slots. -func (queue *priorityQueue) Swap(left, right int) { - queue.items[left], queue.items[right] = queue.items[right], queue.items[left] -} - -// Push appends one heap element. -func (queue *priorityQueue) Push(item any) { - idx, ok := item.(NodeIndex) - if !ok { - panic("commitquery: heap push item is not a NodeIndex") - } - - queue.items = append(queue.items, idx) -} - -// Pop removes one heap element. -func (queue *priorityQueue) Pop() any { - last := len(queue.items) - 1 - item := queue.items[last] - queue.items = queue.items[:last] - - return item -} - -// PushNode inserts one internal node. -func (queue *priorityQueue) PushNode(idx NodeIndex) { - heap.Push(queue, idx) -} - -// PopNode removes the highest-priority internal node. -func (queue *priorityQueue) PopNode() NodeIndex { - item := heap.Pop(queue) - - idx, ok := item.(NodeIndex) - if !ok { - panic("commitquery: heap pop item is not a NodeIndex") - } - - return idx -} diff --git a/internal/commitquery/reduce.go b/internal/commitquery/reduce.go deleted file mode 100644 index 917f2fa2..00000000 --- a/internal/commitquery/reduce.go +++ /dev/null @@ -1,166 +0,0 @@ -package commitquery - -import ( - "slices" -) - -// removeRedundant removes redundant merge-base candidates. -func removeRedundant(ctx *Context, candidates []NodeIndex) ([]NodeIndex, error) { - for _, idx := range candidates { - if ctx.EffectiveGeneration(idx) != generationInfinity { - return removeRedundantWithGen(ctx, candidates), nil - } - } - - return removeRedundantNoGen(ctx, candidates) -} - -func removeRedundantNoGen(ctx *Context, candidates []NodeIndex) ([]NodeIndex, error) { - redundant := make([]bool, len(candidates)) - work := make([]NodeIndex, 0, len(candidates)-1) - filledIndex := make([]int, 0, len(candidates)-1) - - for i, candidate := range candidates { - if redundant[i] { - continue - } - - work = work[:0] - filledIndex = filledIndex[:0] - - minGeneration := ctx.EffectiveGeneration(candidate) - - for j, other := range candidates { - if i == j || redundant[j] { - continue - } - - work = append(work, other) - filledIndex = append(filledIndex, j) - - otherGeneration := ctx.EffectiveGeneration(other) - if otherGeneration < minGeneration { - minGeneration = otherGeneration - } - } - - err := paintDownToCommon(ctx, candidate, work, minGeneration) - if err != nil { - return nil, err - } - - if ctx.HasAnyMarks(candidate, markRight) { - redundant[i] = true - } - - for j, other := range work { - if ctx.HasAnyMarks(other, markLeft) { - redundant[filledIndex[j]] = true - } - } - - ctx.ClearTouchedMarks(allMarks) - } - - out := make([]NodeIndex, 0, len(candidates)) - for i, idx := range candidates { - if !redundant[i] { - out = append(out, idx) - } - } - - return out, nil -} - -func removeRedundantWithGen(ctx *Context, candidates []NodeIndex) []NodeIndex { - sorted := append([]NodeIndex(nil), candidates...) - slices.SortFunc(sorted, compareByGeneration(ctx)) - - minGeneration := ctx.EffectiveGeneration(sorted[0]) - minGenPos := 0 - countStillIndependent := len(candidates) - - ctx.BeginMarkPhase() - - walkStart := make([]NodeIndex, 0, len(candidates)*2) - - for _, idx := range candidates { - ctx.SetMarks(idx, markResult) - - for _, parent := range ctx.Parents(idx) { - if ctx.HasAnyMarks(parent, markStale) { - continue - } - - ctx.SetMarks(parent, markStale) - walkStart = append(walkStart, parent) - } - } - - slices.SortFunc(walkStart, compareByGeneration(ctx)) - - for _, idx := range walkStart { - ctx.ClearMarks(idx, markStale) - } - - for i := len(walkStart) - 1; i >= 0 && countStillIndependent > 1; i-- { - stack := []NodeIndex{walkStart[i]} - ctx.SetMarks(walkStart[i], markStale) - - for len(stack) > 0 { - top := stack[len(stack)-1] - - if ctx.HasAnyMarks(top, markResult) { - ctx.ClearMarks(top, markResult) - - countStillIndependent-- - if countStillIndependent <= 1 { - break - } - - if top == sorted[minGenPos] { - for minGenPos < len(sorted)-1 && ctx.HasAnyMarks(sorted[minGenPos], markStale) { - minGenPos++ - } - - minGeneration = ctx.EffectiveGeneration(sorted[minGenPos]) - } - } - - if ctx.EffectiveGeneration(top) < minGeneration { - stack = stack[:len(stack)-1] - - continue - } - - pushed := false - - for _, parent := range ctx.Parents(top) { - if ctx.HasAnyMarks(parent, markStale) { - continue - } - - ctx.SetMarks(parent, markStale) - stack = append(stack, parent) - pushed = true - - break - } - - if !pushed { - stack = stack[:len(stack)-1] - } - } - } - - out := make([]NodeIndex, 0, len(candidates)) - for _, idx := range candidates { - if !ctx.HasAnyMarks(idx, markStale) { - out = append(out, idx) - } - } - - ctx.ClearTouchedMarks(markStale | markResult) - - return out -} |
