Files
mesh-delivery/modules/mesh-delivery-checker/internal/check/series.go
T

151 lines
5.7 KiB
Go

package check
import (
"context"
"fmt"
"slices"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// A change of a package series is named in the verdict, so the review of the change sees it (design 32 *A package, its
// series*; DLV-C4): for each module a pull request touches, every artifact both its trunk manifest and its head's
// manifest declare whose series differs, as `<module>/<artifact> series <trunk> → <head>`. An artifact that names none
// is in catalogue.DefaultSeries. A manifest that cannot be read is said in the same words, never left out.
// seriesChanges names, on the case per pull request, each change of an artifact's series the pull request makes.
func (st *state) seriesChanges(ctx context.Context) {
for _, p := range st.c.Pulls {
var words []string
var changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
for _, module := range p.Modules {
was, now, ok, err := st.manifestPair(ctx, p, module)
if err != nil {
words = append(words, fmt.Sprintf("%s: its series not compared (%v)", module, err))
continue
}
if ok {
moved := seriesMoved(module, was, now)
changes = append(changes, moved...)
for _, c := range moved {
words = append(words, seriesLine(c))
}
}
}
if len(words) > 0 {
st.c.series(pullKey(p), words, changes)
}
}
}
// manifestPair is a touched module's trunk manifest and its manifest at the pull request's head: the head's by the digest
// its candidate names, else read from the forge at the head. ok is false when the module is new on the pull request,
// unchanged, or not read here.
func (st *state) manifestPair(ctx context.Context, p Pull, module string) (was, now catalogue.Manifest, ok bool, err error) {
if st.j.Read == nil || st.j.Manifests == nil {
return was, now, false, nil
}
trunk := st.j.Manifests.ManifestDigests()[module]
digest := p.Fps[module].Manifest
if trunk == "" || digest == trunk {
return was, now, false, nil
}
if was, err = st.j.Read.Manifest(ctx, trunk); err != nil {
return was, now, false, fmt.Errorf("the trunk's manifest not read: %w", err)
}
switch {
case digest != "":
if now, err = st.j.Read.Manifest(ctx, digest); err != nil {
return was, now, false, fmt.Errorf("the head's manifest not read: %w", err)
}
case st.j.Forge != nil:
var found bool
if now, _, found, err = st.manifestAtHead(ctx, p, module); err != nil || !found {
return was, now, false, err
}
default:
return was, now, false, nil
}
return was, now, true, nil
}
// seriesLine is a series change as the status line names it: `<module>/<artifact> series <trunk> → <head>`.
func seriesLine(c meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) string {
return fmt.Sprintf("%s/%s series %s → %s", c.Module, c.Artifact, c.From, c.To)
}
// seriesMoved are the artifacts of module whose series the head's manifest changes from the trunk's, by artifact. An
// artifact new on the head is no series change (the lead's ruling): it has no series on the trunk.
func seriesMoved(module string, was, now catalogue.Manifest) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
if was.Build == nil || now.Build == nil {
return nil
}
before := map[string]string{}
for _, a := range was.Build.Artifacts {
before[a.Name] = a.SeriesOf()
}
var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
for _, a := range now.Build.Artifacts {
if old, ok := before[a.Name]; ok && old != a.SeriesOf() {
out = append(out, meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{Module: module, Artifact: a.Name, From: old, To: a.SeriesOf()})
}
}
slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int {
return strings.Compare(a.Artifact, b.Artifact)
})
return out
}
// series keeps, of a pull request (pullKey), the words of the series changes it makes and the changes themselves.
func (c *Case) series(key string, words []string, changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) {
c.notesMu.Lock()
defer c.notesMu.Unlock()
if c.seriesOf == nil {
c.seriesOf, c.seriesChanges = map[string][]string{}, map[string][]meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{}
}
c.seriesOf[key], c.seriesChanges[key] = words, changes
}
// SeriesChanges are the series changes a pull request (pullKey) makes, as the verdict and its event name them.
func (c *Case) SeriesChanges(key string) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
c.notesMu.Lock()
defer c.notesMu.Unlock()
return slices.Clone(c.seriesChanges[key])
}
// allSeries are the series changes of every pull request of the case, each module's artifact once, by module and
// artifact: what the verdict names (meshdeliverychecker.VerdictsByGroupHeadsValue.Series).
func (c *Case) allSeries() []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
seen := map[string]bool{}
for _, p := range c.Pulls {
for _, s := range c.SeriesChanges(pullKey(p)) {
if k := s.Module + "/" + s.Artifact; !seen[k] {
seen[k] = true
out = append(out, s)
}
}
}
slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int {
return strings.Compare(a.Module+"/"+a.Artifact, b.Module+"/"+b.Artifact)
})
return out
}
// Series are the series changes a pull request (pullKey) makes, as the check named them; none when it makes none.
func (c *Case) Series(key string) []string {
c.notesMu.Lock()
defer c.notesMu.Unlock()
return slices.Clone(c.seriesOf[key])
}
// seriesWords is a pull request's series changes for its status line.
func seriesWords(c *Case, p Pull) string {
if c == nil {
return ""
}
return strings.Join(c.Series(pullKey(p)), ", ")
}