Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/modules.go
T
jschoubben 9f7e082688 List a repository's modules from the merged message's manifests and the indexer's own bucket, not the controller's build records, so a new module gets a value and a key with no build asked (issue 1851, I1 I2 I4, ADR 0501 §6, ADR 0453 §6)
I1: a merge writes each module its manifests name, at its directory. I2: a pull request's changed directory, a
preview's touched module and a source revision's modules come from the modules bucket (a module a pull request adds is
found in the preview by its manifest). I4: the value of today's form (fp, none) is no longer written, since it was
computed from the build records; mesh-sdk keeps it readable until R4g. One commit: the three share applyMerged and its
tests. Pins mesh-sdk feat/r4e-sdk 9cb4d59.
2026-10-22 18:08:48 +02:00

108 lines
4.4 KiB
Go

package index
import (
"context"
"maps"
"slices"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// applyMerged writes, for each module whose manifest the merged message carries (each directory of the merge commit that
// holds a module.json, the module its manifest names), its value at the merge commit: its directory, its manifest digest,
// its new-form inputs and why it has no new-form fingerprint yet, with the source revision that names the merge commit as
// the repository's head when the indexer has seen it (ApplySource), and Src 0 when it has not. The fingerprint itself is
// computed by Recompute (toolchains.go) once the merge is written, and again when a build module reports its toolchain.
//
// The modules are listed from the message, never from the controller's build records (novox/hq ADR 0501 clause 6, issue
// 1851), so a module added by the merge gets a value, and a fingerprint, with no build anywhere. The value of today's
// form (Module.FP, None) is no longer written: it was computed from those records (ADR 0453 clause 6). A module the
// bucket holds of another repository is left as it is and said: a move between repositories is the operator's (ADR 0420
// clause 7). Two directories naming one module write the one the bucket holds, or the first by name, and say the other.
// A value the module already holds is not written again.
func (ix *Indexer) applyMerged(ctx context.Context, f delivery.Forge) error {
digests, err := ix.keepManifests(ctx, f)
if err != nil {
return err
}
held, err := ix.heldModules(ctx)
if err != nil {
return err
}
overlay := map[string]delivery.Module{}
dirOf := map[string]string{}
for _, dir := range slices.Sorted(maps.Keys(f.Manifests)) {
raw := f.Manifests[dir]
name := manifestModule(raw)
if name == "" {
ix.logf("%s at %s: the manifest in %q names no module, so no module is written for it", f.Repository, f.Merge, dir)
continue
}
key, err := delivery.ModulesKey(name)
if err != nil {
ix.logf("%s: the module %q has no usable key, skipped: %v", f.Repository, name, err)
continue
}
cur, has := held[key]
if has && cur.Repository != "" && !delivery.SameRepository(cur.Repository, f.Repository) {
ix.logf("%s at %s: %q names the module %s, which the indexer holds of %s; left as it is (a move between repositories is the operator's)", f.Repository, f.Merge, dir, name, cur.Repository)
continue
}
if other, dup := dirOf[name]; dup {
if !has || cur.Path != pathOf(dir) {
ix.logf("%s at %s: %q and %q both name the module %s; %q is written", f.Repository, f.Merge, other, dir, name, other)
continue
}
ix.logf("%s at %s: %q and %q both name the module %s; %q, which the indexer holds, is written", f.Repository, f.Merge, other, dir, name, dir)
}
dirOf[name] = dir
next := delivery.Module{Repository: f.Repository, Commit: f.Merge, Src: ix.srcOf(f.Repository, f.Merge), Manifest: digests[dir], Path: pathOf(dir)}
if next.Manifest == "" {
next.None2 = "the manifest cannot be read: it cannot be kept as a value"
} else {
in := inputsOf(name, raw, f.Trees[dir], f.Locks[dir], ix.baseFP(ctx))
next.Inputs2, next.None2 = in.Kept, in.None2
}
overlay[key] = next
}
// What this merge moved reaches the modules standing on it (a base's tree, build-agent's fingerprint), in the graph's order.
return ix.recomputeWith(ctx, overlay)
}
// baseFP answers the new-form fingerprint of a base module as modules.<base> holds it, or why it has none.
func (ix *Indexer) baseFP(ctx context.Context) baseFP {
return func(module string) (string, string) {
key, err := delivery.ModulesKey(module)
if err != nil {
return "", err.Error()
}
value, _, found, err := ix.Modules.Get(ctx, key)
if err != nil {
return "", "its record cannot be read: " + err.Error()
}
if !found {
return "", "the indexer holds no record of it"
}
cur, err := delivery.DecodeModule(value)
switch {
case err != nil:
return "", "its record cannot be read: " + err.Error()
case cur.FP2 != "":
return cur.FP2, ""
case cur.None2 != "":
return "", cur.None2
}
return "", "its record carries no fingerprint of the new form"
}
}
// pathOf is the module's directory as Module.Path holds it: "." for the repository's root, since empty means not known.
func pathOf(dir string) string {
if dir == "" {
return delivery.RootPath
}
return dir
}
func withV(m delivery.Module) delivery.Module { m.V = delivery.Version; return m }