Compare commits

...

1 Commits

Author SHA1 Message Date
Andrey Kumanyaev 773101b3e5 fix(indexer): use incremental contract cleanup on untrack 2026-08-15 17:14:41 +02:00
2 changed files with 57 additions and 1 deletions
+7 -1
View File
@@ -3203,6 +3203,10 @@ func (mi *MultiIndexer) UntrackRepo(repoPrefix string) (int, int) {
mi.mu.Unlock()
return 0, 0
}
// Snapshot the exact derived-contract frontier before deleting the repo.
// Cross-repo bridge nodes can be owned by a surviving repo, so purge alone
// cannot remove every dangling derived edge.
contractPlan := mi.contractInvalidationPlanForRepo(idx)
delete(mi.repos, repoPrefix)
delete(mi.indexers, repoPrefix)
mi.mu.Unlock()
@@ -3266,7 +3270,9 @@ func (mi *MultiIndexer) UntrackRepo(repoPrefix string) (int, int) {
}
}
mi.ReconcileContractEdges()
if !contractPlan.Empty() {
mi.ReconcileContractEdgesForFrontier(contractPlan)
}
// Keep the closed slot installed until every purge/config side effect is
// complete, then remove only the exact generation drained above. A stale
@@ -0,0 +1,50 @@
package indexer
import "github.com/zzet/gortex/internal/graph"
// contractInvalidationPlanForRepo snapshots the derived contract frontier
// before the repository registry and graph nodes are removed. Cross-repository
// bridge edges may be owned by a surviving repository, so the removed contract
// IDs alone are not enough; include every bridge that points at them.
func (mi *MultiIndexer) contractInvalidationPlanForRepo(idx *Indexer) DerivedInvalidationPlan {
var plan DerivedInvalidationPlan
if idx == nil {
return plan
}
registry := idx.ContractRegistry()
if registry == nil {
return plan
}
contractIDs := make(map[string]struct{})
for _, contract := range registry.All() {
if contract.ID == "" {
continue
}
plan.ContractGroups = append(plan.ContractGroups, ContractGroupFrontier{
WorkspaceID: contract.EffectiveWorkspace(),
ProjectID: contract.EffectiveProject(),
ContractID: contract.ID,
})
if contract.SymbolID != "" {
plan.ContractSymbolIDs = append(plan.ContractSymbolIDs, contract.SymbolID)
}
contractIDs[contract.ID] = struct{}{}
}
if len(contractIDs) == 0 {
return plan
}
incoming := mi.graph.GetInEdgesByNodeIDs(sortedStringKeys(contractIDs))
bridgeIDs := make(map[string]struct{})
for _, edges := range incoming {
for _, edge := range edges {
if edge != nil && edge.Kind == graph.EdgeBridges && edge.From != "" {
bridgeIDs[edge.From] = struct{}{}
}
}
}
plan.ContractBridgeNodeIDs = sortedStringKeys(bridgeIDs)
return plan
}