171 lines
5.5 KiB
Go
171 lines
5.5 KiB
Go
package engine
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
)
|
|
|
|
type inheritanceHistoryIndex struct {
|
|
paths pathPrefixIndex
|
|
}
|
|
|
|
func newInheritanceHistoryIndex(operations []inheritanceOperation) *inheritanceHistoryIndex {
|
|
if len(operations) == 0 {
|
|
return nil
|
|
}
|
|
index := &inheritanceHistoryIndex{}
|
|
for operationID, operation := range operations {
|
|
if operation.control != nil {
|
|
index.paths.insert(operation.control.path.String(), operationID)
|
|
continue
|
|
}
|
|
if operation.transfer != nil {
|
|
index.paths.insert(operation.transfer.to.String(), operationID)
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (index *inheritanceHistoryIndex) matchingOperations(path Path, operations []inheritanceOperation) []inheritanceOperation {
|
|
if index == nil {
|
|
return operations
|
|
}
|
|
var ids []int
|
|
seen := make(map[int]struct{})
|
|
collect := func(id int) {
|
|
if _, found := seen[id]; found {
|
|
return
|
|
}
|
|
seen[id] = struct{}{}
|
|
ids = append(ids, id)
|
|
}
|
|
pointer := path.String()
|
|
index.paths.visitAncestors(pointer, collect)
|
|
index.paths.visitDescendants(pointer, collect)
|
|
sort.Ints(ids)
|
|
matching := make([]inheritanceOperation, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id >= 0 && id < len(operations) {
|
|
matching = append(matching, operations[id])
|
|
}
|
|
}
|
|
return matching
|
|
}
|
|
|
|
// Retain source history at original paths. Structural operations are held once
|
|
// per relationship and projected onto a requested destination path only when
|
|
// History or Explain is queried.
|
|
func applyInheritanceMetadata(metadata *Metadata, raw *document.Node, rawOrigins map[string]Origin, overlays []overlayRecord, expansion *inheritanceScope) {
|
|
if metadata == nil || expansion == nil {
|
|
return
|
|
}
|
|
sourceView := Metadata{
|
|
fields: make(map[string]FieldInfo),
|
|
history: metadata.history,
|
|
}
|
|
walkDocument(raw, Path{}, func(path Path, node *document.Node) {
|
|
origin, found := rawOrigins[path.String()]
|
|
if !found {
|
|
origin = Origin{Layer: LayerBase, Locations: []Location{publicLocation(node.Location)}}
|
|
}
|
|
sourceView.fields[path.String()] = FieldInfo{Explicit: true, Null: node.Kind == document.Null, Origin: cloneOrigin(origin)}
|
|
})
|
|
applyOverlayMetadata(&sourceView, overlays)
|
|
metadata.history = sourceView.history
|
|
metadata.inheritance = cloneInheritanceOperations(expansion.operations)
|
|
}
|
|
|
|
func historyWithInheritance(path Path, stored []Change, operations []inheritanceOperation) []Change {
|
|
split := len(stored)
|
|
for index, change := range stored {
|
|
if change.Origin.Layer == LayerCLI || change.Origin.Layer == LayerResolution {
|
|
split = index
|
|
break
|
|
}
|
|
}
|
|
history := cloneChanges(stored[:split])
|
|
for _, operation := range operations {
|
|
for _, change := range projectedInheritanceChanges(path, operation) {
|
|
if len(history) != 0 {
|
|
change.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
}
|
|
history = append(history, change)
|
|
}
|
|
}
|
|
for _, change := range stored[split:] {
|
|
cloned := cloneChange(change)
|
|
if len(history) != 0 {
|
|
cloned.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
}
|
|
history = append(history, cloned)
|
|
}
|
|
return history
|
|
}
|
|
|
|
func projectedInheritanceChanges(query Path, operation inheritanceOperation) []Change {
|
|
if operation.control != nil {
|
|
if !pathsIntersect(operation.control.path, query) {
|
|
return nil
|
|
}
|
|
origin := cloneOrigin(operation.controlOrigin)
|
|
origin.Operation = ChangeInheritance
|
|
origin.Inheritance = projectInheritanceSteps(origin)
|
|
return []Change{{Path: operation.control.path.clone(), Origin: origin,
|
|
Dependencies: []Path{operation.control.path.clone()}}}
|
|
}
|
|
if operation.transfer == nil || !pathsIntersect(operation.transfer.to, query) {
|
|
return nil
|
|
}
|
|
path := query
|
|
if pathContains(query, operation.transfer.to) {
|
|
path = operation.transfer.to
|
|
}
|
|
var changes []Change
|
|
parent, parentFound := transferredOperationOrigin(operation, path)
|
|
if parentFound {
|
|
parent.Operation = ChangeInheritance
|
|
parent.Inheritance = projectInheritanceSteps(parent)
|
|
changes = append(changes, Change{
|
|
Path: path.clone(), Origin: parent,
|
|
Dependencies: []Path{operation.transfer.from.clone(), operation.transfer.reference.clone()},
|
|
})
|
|
}
|
|
effective, effectiveFound := operation.effectiveOrigins[path.String()]
|
|
if effectiveFound {
|
|
effective = cloneOrigin(effective)
|
|
effective.Operation = ChangeInheritance
|
|
}
|
|
if effectiveFound && (!parentFound || !originContainedBy(effective, parent)) {
|
|
effective.Inheritance = projectInheritanceSteps(effective)
|
|
changes = append(changes, Change{
|
|
Path: path.clone(), Origin: effective,
|
|
Dependencies: []Path{operation.transfer.reference.clone()},
|
|
})
|
|
} else if parentFound && !effectiveFound {
|
|
removed := cloneOrigin(operation.effectiveOrigins[operation.transfer.to.String()])
|
|
removed.Operation = ChangeInheritance
|
|
removed.Inheritance = projectInheritanceSteps(removed)
|
|
changes = append(changes, Change{
|
|
Path: operation.transfer.to.clone(), Origin: removed,
|
|
Dependencies: []Path{operation.transfer.reference.clone()},
|
|
})
|
|
}
|
|
return changes
|
|
}
|
|
|
|
func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) {
|
|
relative, ok := relativeHistoryPath(operation.transfer.to, destination)
|
|
if !ok {
|
|
return Origin{}, false
|
|
}
|
|
source := appendInheritancePath(operation.transfer.from, relative)
|
|
origin, found := operation.parentOrigins[source.String()]
|
|
if !found {
|
|
return Origin{}, false
|
|
}
|
|
transferred := transferredInheritanceOrigins(map[string]Origin{source.String(): origin},
|
|
operation.transfer.from, operation.transfer.to, *operation.transfer)
|
|
return transferred[destination.String()], true
|
|
}
|