116 lines
4.2 KiB
Go
116 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// The pull request's key (novox/hq ADR 0406 §4, issue 975): mesh-delivery keeps one key per pull request on the bus,
|
|
// holding its head, the trunk commit its current check is asked against and its verdict, written by compare-and-set.
|
|
// It writes a new head when the forge says one (`pull.updated`, which carries the head and the trunk commit it was
|
|
// announced against), and a new trunk commit only when the check is asked again against one — the merge tool's ask is
|
|
// that same announcement. A merge of a group's member, and a trunk move that leaves an inactive pull request unasked,
|
|
// are announced as nothing (the gitea module announces `pull.updated` only for a head and for the merge tool's ask), so
|
|
// they write nothing. A verdict for that head and trunk commit writes the verdict. The build seat reads the key.
|
|
|
|
// PullKey is what the bus keeps of one pull request.
|
|
type PullKey struct {
|
|
Head string `json:"head"`
|
|
Trunk string `json:"trunk,omitempty"`
|
|
Verdict string `json:"verdict,omitempty"`
|
|
}
|
|
|
|
// PullKeys keeps the pull requests' keys: Change reads the key, applies the change to what it read and writes the result
|
|
// by compare-and-set when the change says to write; a write another writer won reads the key again and applies again.
|
|
type PullKeys interface {
|
|
Change(key string, apply func(PullKey) (PullKey, bool)) error
|
|
}
|
|
|
|
// pullKeyName is a pull request's key: owner.repo.number, with any dot in a name made an underscore.
|
|
func pullKeyName(owner, repo string, number int) string {
|
|
clean := func(s string) string { return strings.NewReplacer(".", "_", "/", "_").Replace(s) }
|
|
return fmt.Sprintf("%s.%s.%d", clean(owner), clean(repo), number)
|
|
}
|
|
|
|
// KeepPull writes the key of a pull request whose head was announced, when the head or the trunk commit it was
|
|
// announced against is not what the key holds; a new head or trunk clears the verdict.
|
|
func (h *Holder) KeepPull(p PullEvent) {
|
|
if h.Pulls == nil || p.Number == 0 || p.HeadSHA == "" {
|
|
return
|
|
}
|
|
err := h.Pulls.Change(pullKeyName(p.Owner, p.Repo, p.Number), func(k PullKey) (PullKey, bool) {
|
|
if sameCommit(k.Head, p.HeadSHA) && k.Head != "" && (p.BaseSHA == "" || sameCommit(k.Trunk, p.BaseSHA)) {
|
|
return k, false
|
|
}
|
|
return PullKey{Head: p.HeadSHA, Trunk: p.BaseSHA}, true
|
|
})
|
|
if err != nil {
|
|
h.Logf("[mesh-delivery] the key of %s/%s#%d could not be written: %v", p.Owner, p.Repo, p.Number, err)
|
|
h.again("a pull request's key that could not be written")
|
|
}
|
|
}
|
|
|
|
// KeepVerdict writes a verdict into the key of its pull request, when the key holds that head and trunk commit.
|
|
func (h *Holder) KeepVerdict(c CheckedEvent) {
|
|
if h.Pulls == nil || c.Group != "" || c.Number == 0 || c.Commit == "" || c.Trunk == "" {
|
|
return
|
|
}
|
|
verdict := c.Verdict
|
|
if verdict == "" {
|
|
return
|
|
}
|
|
err := h.Pulls.Change(pullKeyName(c.Owner, c.Repo, c.Number), func(k PullKey) (PullKey, bool) {
|
|
if !sameCommit(k.Head, c.Commit) || !sameCommit(k.Trunk, c.Trunk) || k.Verdict == verdict {
|
|
return k, false
|
|
}
|
|
k.Verdict = verdict
|
|
return k, true
|
|
})
|
|
if err != nil {
|
|
h.Logf("[mesh-delivery] the verdict in the key of %s/%s#%d could not be written: %v", c.Owner, c.Repo, c.Number, err)
|
|
h.again("a pull request's key that could not be written")
|
|
}
|
|
}
|
|
|
|
// kvPulls keeps the keys in the module's own state `pulls`.
|
|
type kvPulls struct{}
|
|
|
|
// changeAttempts is how often a lost compare-and-set is read and applied again before it is said.
|
|
const changeAttempts = 10
|
|
|
|
func (kvPulls) Change(key string, apply func(PullKey) (PullKey, bool)) error {
|
|
state := stdio.State("pulls")
|
|
for range changeAttempts {
|
|
e, err := state.Get(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var k PullKey
|
|
if e != nil {
|
|
if err := json.Unmarshal(e.Value, &k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
next, write := apply(k)
|
|
if !write {
|
|
return nil
|
|
}
|
|
if e == nil {
|
|
_, err = state.Create(key, next)
|
|
} else {
|
|
_, err = state.Update(key, next, e.Revision)
|
|
}
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !errors.Is(err, stdio.ErrChanged) {
|
|
return err
|
|
}
|
|
}
|
|
return fmt.Errorf("%s was written by another writer first %d times: %w", key, changeAttempts, stdio.ErrChanged)
|
|
}
|