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

136 lines
4.5 KiB
Go

package check
import (
"fmt"
"maps"
"sort"
"sync"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// HQRepository is the repository the dark checker writes no status for (ADR 0484 clause 8): hq builds no module and its
// check is a script no build module can run, so it is judged by its merge-check.sh until issue 1296 settles how.
const HQRepository = "novox/hq"
// HQNote is what the read tools say of an hq pull request (ADR 0484 clause 8).
const HQNote = "hq: judged by merge-check.sh until issue 1296"
// Pull is one pull request the checker judges: its head, the trunk head of its repository it is merged onto and the
// modules the indexer says it touches, with the fingerprints the indexer wrote for them.
type Pull struct {
Repository string
Number int
Head string
Trunk string
Modules []string
Fps map[string]delivery.CandidateModule
}
// Case is one check: a candidate's pull requests that are judged (those of hq and of a repository whose trunk is not known
// are left out), the trunk head of each repository and the key of the verdict.
type Case struct {
// Key is the verdict key, `<group>.<heads hash>`.
Key string
Group string
HeadsHash string
Pulls []Pull
// Trunk is the trunk head of each repository of Pulls.
Trunk map[string]string
// Left says why pull requests of the candidate are not judged (hq's, a repository with no known trunk); empty when all are.
Left []string
// Queued is when the checker took the case up, for the time of its queue.
Queued time.Time
// Progress says what the check is doing, so a check that ran past its bound names the step it was stuck at.
Progress *Progress
}
// Progress is the step a check is at, written by the check and read when its bound passes.
type Progress struct {
mu sync.Mutex
stage, node string
}
// Set records the step now running and the node it runs on, empty for none.
func (p *Progress) Set(stage, node string) {
p.mu.Lock()
p.stage, p.node = stage, node
p.mu.Unlock()
}
// Now is the step recorded last and its node.
func (p *Progress) Now() (stage, node string) {
p.mu.Lock()
defer p.mu.Unlock()
return p.stage, p.node
}
// CaseOf is the check of a candidate against the trunk heads (the gate's source.main): the pull requests that are judged.
// The checker judges pull requests into main only (ADR 0484 clause 11): the pull message carries no base, a merge into
// another branch is a closed message, so every candidate is into the trunk; a repository whose trunk head is not known has
// none to merge onto. It answers nil when no pull request is left to judge.
func CaseOf(c delivery.Candidate, trunk map[string]string) *Case {
key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
if err != nil {
return nil
}
cs := &Case{Key: key, Group: c.Group, HeadsHash: c.HeadsHash, Trunk: map[string]string{}, Progress: &Progress{}}
for _, p := range c.Pulls {
switch head, known := trunk[p.Repository]; {
case p.Repository == HQRepository:
cs.Left = append(cs.Left, fmt.Sprintf("%s#%d: %s", p.Repository, p.Number, HQNote))
case !known || head == "":
cs.Left = append(cs.Left, fmt.Sprintf("%s#%d: the trunk head of %s is not known", p.Repository, p.Number, p.Repository))
default:
cs.Pulls = append(cs.Pulls, Pull{Repository: p.Repository, Number: p.Number, Head: p.Head.Head, Trunk: head, Modules: p.Modules, Fps: p.Fps})
cs.Trunk[p.Repository] = head
}
}
if len(cs.Pulls) == 0 {
return nil
}
sort.SliceStable(cs.Pulls, func(i, j int) bool {
if cs.Pulls[i].Repository != cs.Pulls[j].Repository {
return cs.Pulls[i].Repository < cs.Pulls[j].Repository
}
return cs.Pulls[i].Number < cs.Pulls[j].Number
})
return cs
}
// sameTrunk says whether two verdicts' trunk heads are the same.
func sameTrunk(a, b map[string]string) bool { return maps.Equal(a, b) }
// Trunks holds the trunk head of each repository, as the gate's source.main says (a watch puts it in).
type Trunks struct {
mu sync.Mutex
heads map[string]string
}
// Put takes a source.main value in and answers whether the heads changed.
func (t *Trunks) Put(value []byte, deleted bool) bool {
var heads map[string]string
if !deleted {
s, err := delivery.DecodeSource(value)
if err != nil {
return false
}
heads = s.Heads
}
t.mu.Lock()
defer t.mu.Unlock()
if maps.Equal(t.heads, heads) {
return false
}
t.heads = maps.Clone(heads)
return true
}
// Heads are the trunk heads now (a copy).
func (t *Trunks) Heads() map[string]string {
t.mu.Lock()
defer t.mu.Unlock()
return maps.Clone(t.heads)
}