Preserve inherited source origins and transfer evidence
This commit is contained in:
127
internal/engine/inheritance_origin.go
Normal file
127
internal/engine/inheritance_origin.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
)
|
||||
|
||||
func directInheritanceOrigins(node *document.Node, root Path, input map[string]Origin) map[string]Origin {
|
||||
result := make(map[string]Origin)
|
||||
walkDocument(node, root, func(path Path, value *document.Node) {
|
||||
origin, found := input[path.String()]
|
||||
if !found {
|
||||
origin = Origin{Layer: LayerBase, Locations: []Location{publicLocation(value.Location)}}
|
||||
}
|
||||
origin = cloneOrigin(origin)
|
||||
if len(origin.contributors) == 0 {
|
||||
origin.contributors = make([]inheritanceContributor, len(origin.Locations))
|
||||
for index, location := range origin.Locations {
|
||||
origin.contributors[index] = inheritanceContributor{
|
||||
source: path.clone(), location: location,
|
||||
}
|
||||
}
|
||||
}
|
||||
result[path.String()] = origin
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func transferredInheritanceOrigins(parent map[string]Origin, from, to Path, control inheritanceTransfer) map[string]Origin {
|
||||
result := make(map[string]Origin, len(parent))
|
||||
transfer := control // one immutable edge shared by every matching contributor
|
||||
links := make(map[*inheritanceTrace]*inheritanceTrace)
|
||||
for pointer, origin := range parent {
|
||||
path, err := ParsePath(pointer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
relative, ok := relativeHistoryPath(from, path)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
destination := appendInheritancePath(to, relative)
|
||||
origin = cloneOrigin(origin)
|
||||
for index, contributor := range origin.contributors {
|
||||
link, found := links[contributor.trace]
|
||||
if !found {
|
||||
link = &inheritanceTrace{previous: contributor.trace, transfer: &transfer}
|
||||
links[contributor.trace] = link
|
||||
}
|
||||
origin.contributors[index].trace = link
|
||||
}
|
||||
result[destination.String()] = origin
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func appendInheritancePath(root, relative Path) Path {
|
||||
return Path{segments: append(clonePathSegments(root.segments, len(relative.segments)), relative.segments...)}
|
||||
}
|
||||
|
||||
func mergeInheritanceOrigin(origins map[string]Origin, path Path, incoming Origin) {
|
||||
pointer := path.String()
|
||||
current := origins[pointer]
|
||||
current.Layer = incoming.Layer
|
||||
current.Locations = append(current.Locations, incoming.Locations...)
|
||||
current.contributors = append(current.contributors, incoming.contributors...)
|
||||
origins[pointer] = current
|
||||
}
|
||||
|
||||
func copyInheritanceSubtree(target, incoming map[string]Origin, node *document.Node, root Path) {
|
||||
walkDocument(node, root, func(path Path, _ *document.Node) {
|
||||
target[path.String()] = cloneOrigin(incoming[path.String()])
|
||||
})
|
||||
}
|
||||
|
||||
// Materialize concrete steps only for an inspection record. The linked private
|
||||
// traces remain compact when one parent supplies many descendant values.
|
||||
func projectInheritanceSteps(origin Origin) []InheritanceStep {
|
||||
steps := cloneInheritanceSteps(origin.Inheritance)
|
||||
for _, contributor := range origin.contributors {
|
||||
var reverse []*inheritanceTrace
|
||||
for link := contributor.trace; link != nil; link = link.previous {
|
||||
reverse = append(reverse, link)
|
||||
}
|
||||
current := contributor.source
|
||||
for index := len(reverse) - 1; index >= 0; index-- {
|
||||
transfer := reverse[index].transfer
|
||||
relative, ok := relativeHistoryPath(transfer.from, current)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
next := appendInheritancePath(transfer.to, relative)
|
||||
step := InheritanceStep{
|
||||
From: current.clone(), To: next.clone(), Reference: transfer.reference.clone(),
|
||||
Locations: cloneLocations(transfer.locations),
|
||||
}
|
||||
if !slices.ContainsFunc(steps, func(existing InheritanceStep) bool {
|
||||
return existing.From.String() == step.From.String() &&
|
||||
existing.To.String() == step.To.String() &&
|
||||
existing.Reference.String() == step.Reference.String() &&
|
||||
slices.Equal(existing.Locations, step.Locations)
|
||||
}) {
|
||||
steps = append(steps, step)
|
||||
}
|
||||
current = next
|
||||
}
|
||||
}
|
||||
return steps
|
||||
}
|
||||
|
||||
func sameInheritanceTrace(left, right *inheritanceTrace) bool {
|
||||
for left != nil && right != nil {
|
||||
if left == right {
|
||||
return true
|
||||
}
|
||||
l, r := left.transfer, right.transfer
|
||||
if !slices.Equal(l.from.segments, r.from.segments) ||
|
||||
!slices.Equal(l.to.segments, r.to.segments) ||
|
||||
!slices.Equal(l.reference.segments, r.reference.segments) ||
|
||||
!slices.Equal(l.locations, r.locations) {
|
||||
return false
|
||||
}
|
||||
left, right = left.previous, right.previous
|
||||
}
|
||||
return left == nil && right == nil
|
||||
}
|
||||
Reference in New Issue
Block a user