136 lines
4.5 KiB
Go
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)
|
|
}
|