mesh/delivery merged: source.main moved to novox/mesh-catalog@9bd824791509
mesh/check being carried to 116751130f5c on 9bd824791509 | from c05aa5e6eafd
183 lines
5.8 KiB
Go
183 lines
5.8 KiB
Go
package main
|
|
|
|
// A fix whose pull request the tracker keeps no record for (merged before the tracker heard the delivery, or linked after it
|
|
// moved) learns its merge here, so the view's `delivered` event can cover it like any recorded fix (issue 1950). The delivery
|
|
// is asked first (its `checks` carries the forge's own pull request beside its delivery); the forge is asked when the delivery
|
|
// cannot say. One that neither can say is left for the next time and logged; a pull request closed without merging becomes
|
|
// the withdrawn state the tracker already knows.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// pullFacts reads the forge's pull request block, as gitea_get_pull_request and mesh-delivery's checks both carry it.
|
|
type pullFacts struct {
|
|
Merged bool `json:"merged"`
|
|
State string `json:"state"`
|
|
MergeCommitSHA string `json:"merge_commit_sha"`
|
|
MergedAt *time.Time `json:"merged_at"`
|
|
}
|
|
|
|
// fromPull completes a delivery with what the forge's pull request says: whether it is merged or closed, the commit its
|
|
// merge landed as and when.
|
|
func (d *Delivery) fromPull(p pullFacts, repository string) {
|
|
d.Pulled = true
|
|
d.Merged = p.Merged
|
|
d.Closed = p.State == "closed" && !p.Merged
|
|
if d.Repository == "" {
|
|
d.Repository = strings.ToLower(repository)
|
|
}
|
|
if p.Merged {
|
|
if d.MergedAs == "" {
|
|
d.MergedAs = p.MergeCommitSHA
|
|
}
|
|
if d.MergedAt == nil {
|
|
d.MergedAt = nonZero(p.MergedAt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// busPull asks the forge for one pull request (gitea_get_pull_request).
|
|
func busPull(owner string) DeliveryState {
|
|
return func(repository, number string) (Delivery, error) {
|
|
var n int
|
|
if _, err := fmt.Sscanf(number, "%d", &n); err != nil || n <= 0 {
|
|
return Delivery{}, fmt.Errorf("%q is not a pull request number", number)
|
|
}
|
|
raw, err := stdio.Ask("gitea.gitea_get_pull_request", map[string]any{"owner": owner, "repo": repository, "number": n})
|
|
if err != nil {
|
|
return Delivery{}, err
|
|
}
|
|
var out struct {
|
|
Pull *pullFacts `json:"pull"`
|
|
Answer struct {
|
|
Pull *pullFacts `json:"pull"`
|
|
} `json:"answer"`
|
|
}
|
|
if json.Unmarshal(raw, &out) != nil {
|
|
return Delivery{}, errors.New("the forge's gitea_get_pull_request is not readable")
|
|
}
|
|
p := out.Pull
|
|
if p == nil {
|
|
p = out.Answer.Pull
|
|
}
|
|
if p == nil {
|
|
return Delivery{}, errors.New("the forge's gitea_get_pull_request answered no pull request")
|
|
}
|
|
var d Delivery
|
|
d.fromPull(*p, owner+"/"+repository)
|
|
return d, nil
|
|
}
|
|
}
|
|
|
|
// learnFix gives a pull request an issue names and keeps no Fix record for its record: from the delivery's own state when it has
|
|
// one, else from the pull request itself (the delivery's, then the forge's). It answers whether a record was kept. A pull request
|
|
// still open is not kept (its own events say it when they come); one neither can say anything of is left for next time.
|
|
func (t *Tracker) learnFix(n uint64, pr string) (bool, error) {
|
|
repo, number, _ := strings.Cut(pr, "#")
|
|
var d Delivery
|
|
var derr error
|
|
if t.Deliveries != nil {
|
|
d, derr = t.Deliveries(repo, number)
|
|
}
|
|
if derr == nil && d.State != "" {
|
|
since := d.Since
|
|
if since.IsZero() {
|
|
since = t.now()
|
|
}
|
|
ch, err := t.recordFix(pr, d.heard(since), n)
|
|
return len(ch) > 0, err
|
|
}
|
|
if derr != nil || !d.Pulled {
|
|
if t.Pull == nil {
|
|
return false, derr
|
|
}
|
|
f, ferr := t.Pull(repo, number)
|
|
if ferr != nil {
|
|
if derr != nil {
|
|
return false, derr
|
|
}
|
|
t.Logf("[mesh-issues] the pull request %s (issue %d) could not be read from the forge (asked again later): %v", pr, n, ferr)
|
|
return false, nil
|
|
}
|
|
d = f
|
|
}
|
|
var h heardFix
|
|
switch {
|
|
case d.Merged:
|
|
// Kept as of now, the moment it was learned: the fix's state did not change days ago for the tracker, and the stuck
|
|
// sweep measures from it. MergedAt stays the forge's own moment.
|
|
h = heardFix{State: "merged", Merged: true, At: t.now(), MergedAt: d.MergedAt, Repository: d.Repository, MergedAs: d.MergedAs}
|
|
case d.Closed:
|
|
h = heardFix{State: "closed", Why: "closed without merging", At: t.now()}
|
|
default:
|
|
return false, nil
|
|
}
|
|
ch, err := t.recordFix(pr, h, n)
|
|
return len(ch) > 0, err
|
|
}
|
|
|
|
// learnUnheard learns, for at most most pull requests, each fix of issue n with no record; it answers whether any was kept.
|
|
func (t *Tracker) learnUnheard(n uint64, is *Issue, most int) (int, bool, error) {
|
|
asked, kept := 0, false
|
|
for _, pr := range fixPullRequests(is) {
|
|
if fixOf(is, pr) != nil {
|
|
continue
|
|
}
|
|
if asked == most {
|
|
break
|
|
}
|
|
asked++
|
|
ok, err := t.learnFix(n, pr)
|
|
if err != nil {
|
|
return asked, kept, fmt.Errorf("the delivery of %s (issue %d) could not be asked: %v", pr, n, err)
|
|
}
|
|
kept = kept || ok
|
|
}
|
|
return asked, kept, nil
|
|
}
|
|
|
|
// LearnFixes is the tool that asks again for one issue: every pull request its fixed-by names with no record is asked of the
|
|
// delivery and the forge now. It answers what each is kept as.
|
|
func (t *Tracker) LearnFixes(n uint64) (map[string]any, error) {
|
|
is, _, err := read(t.Store, n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, _, err := t.learnUnheard(n, is, MostSweepAsks*2); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := t.coverReached(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := t.afterFix(n); err != nil {
|
|
return nil, err
|
|
}
|
|
is, _, err = read(t.Store, n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]string{}
|
|
for _, pr := range fixPullRequests(is) {
|
|
switch f := fixOf(is, pr); {
|
|
case f == nil:
|
|
out[pr] = "nothing heard: neither the delivery nor the forge said"
|
|
case withdrawn(f):
|
|
out[pr] = "withdrawn: closed without merging"
|
|
case contains(is.Delivered, pr):
|
|
out[pr] = "delivered"
|
|
case f.Merged:
|
|
out[pr] = "merged as " + shortCommit(f.MergedAs) + ", not delivered"
|
|
default:
|
|
out[pr] = f.State
|
|
}
|
|
}
|
|
return map[string]any{"number": n, "fixes": out, "delivery": DeliveryLine(is)}, nil
|
|
}
|