73 lines
2.8 KiB
Go
73 lines
2.8 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/composer"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The check refuses a change whose module graph has a cycle and names it (novox/hq ADR 0603 §2): modules that would each
|
|
// wait for the other for good, through their bases or the provisions they await. The graph is worked out from the
|
|
// catalogue as the change leaves it (every trunk manifest, each one a pull request touches replaced by its candidate's),
|
|
// with the contract's ManifestGraph, the way the indexer works out its graph's edges. A cycle the trunk already has among
|
|
// the same modules is the trunk's: said in the verdict's notes, not the change's failure.
|
|
|
|
// ManifestReader reads a kept manifest by its digest (the indexer's manifests state).
|
|
type ManifestReader interface {
|
|
Manifest(ctx context.Context, digest string) (catalogue.Manifest, error)
|
|
}
|
|
|
|
// refuseCycle fails the check for a cycle of the change's module graph, naming it; nil when the case touches no manifest,
|
|
// when nothing reads manifests, or when there is none. A manifest that cannot be read fails the check, said, never passes it.
|
|
func (st *state) refuseCycle(ctx context.Context) *delivery.Verdict {
|
|
if st.j.Manifests == nil || st.j.Read == nil || st.c.Try != nil {
|
|
return nil
|
|
}
|
|
trunk := st.j.Manifests.ManifestDigests()
|
|
req, touched := judgeRequest(trunk, st.c.Pulls)
|
|
if len(touched) == 0 {
|
|
return nil
|
|
}
|
|
read := func(digests map[string]composer.Manifest) (map[string]catalogue.Manifest, error) {
|
|
out := map[string]catalogue.Manifest{}
|
|
for _, module := range slices.Sorted(maps.Keys(digests)) {
|
|
m, err := st.j.Read.Manifest(ctx, digests[module].Digest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the manifest %s of %s: %w", digests[module].Digest, module, err)
|
|
}
|
|
out[module] = m
|
|
}
|
|
return out, nil
|
|
}
|
|
change, err := read(req.Cases[0].Manifests)
|
|
if err != nil {
|
|
return fail(goModule, "the change's module graph cannot be worked out, so no cycle can be ruled out: "+err.Error())
|
|
}
|
|
cycle := delivery.ManifestGraph(change).Cycle()
|
|
if cycle == nil {
|
|
st.c.note("the change's module graph has no cycle")
|
|
return nil
|
|
}
|
|
words := strings.Join(cycle, " -> ")
|
|
if before, err := read(req.Cases[1].Manifests); err == nil {
|
|
among := delivery.Graph{}
|
|
for name, n := range delivery.ManifestGraph(before) {
|
|
if slices.Contains(cycle, name) {
|
|
among[name] = n
|
|
}
|
|
}
|
|
if among.Cycle() != nil {
|
|
st.c.note("the module graph has the cycle " + words + ", which the trunk already has among the same modules (ADR 0603 §2)")
|
|
return nil
|
|
}
|
|
}
|
|
return fail(goModule, fmt.Sprintf("the change's module graph has the cycle %s: these modules would each wait for the other for "+
|
|
"good, through their bases or the provisions they await (ADR 0603 §2)", words))
|
|
}
|