issue 975: ready marks, verdicts and Approves ask the merge as events, not at the next minute
This commit is contained in:
@@ -442,6 +442,7 @@ func (h *Holder) decided(id, state string, w *asks.Warrant) []func() {
|
||||
for _, d := range covered {
|
||||
r := d.Ready
|
||||
r.Warrant = w.Says()
|
||||
h.mergeAgainNow(d) // the operator chose: the merge is asked at once
|
||||
switch option.ID {
|
||||
case OptionApprove:
|
||||
r.Approved = &now
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
)
|
||||
|
||||
// Events that start a step (novox/hq ADR 0406 §1, issue 975).
|
||||
//
|
||||
// This owner acts on events: a ready mark, a verdict, an Approve, a group's turn. The forge's label and comment events
|
||||
// say that a pull request's ready mark may have changed; they do not say whether it counts. Which accounts' "ready at"
|
||||
// comments count is the forge module's rule (its trusted setting ready-authors, the gitea module's readyMarkOf), so a
|
||||
// label added or a "ready at" comment written makes this owner read the forge's open pull requests once, and the mark
|
||||
// it reads there is the mark (seeMarks). The label taken off needs no read: it only clears.
|
||||
|
||||
// ForgePullEvent is the forge module's `forge.pull-request.labeled`, `.unlabeled` and `.commented`
|
||||
// (modules/gitea/webhook.ts): the pull request, and the label added or taken off, or the comment written.
|
||||
type ForgePullEvent struct {
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Number int `json:"number"`
|
||||
HeadSHA string `json:"head_sha,omitempty"`
|
||||
Base string `json:"base,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
CommentID int `json:"comment_id,omitempty"`
|
||||
}
|
||||
|
||||
// ForgeStatusEvent is the forge module's `forge.status`: a commit's status was set.
|
||||
type ForgeStatusEvent struct {
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
SHA string `json:"sha"`
|
||||
Context string `json:"context"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// readyAtComment is a comment that names the head a pull request is marked ready at, as the forge module reads it
|
||||
// (modules/gitea/client.ts READY_AT): "ready at <full sha>" on a line of its own.
|
||||
var readyAtComment = regexp.MustCompile(`(?im)^\s*ready at ([0-9a-f]{40}|[0-9a-f]{64})\b`)
|
||||
|
||||
func eventPullKey(e ForgePullEvent) string {
|
||||
return strings.ToLower(e.Owner+"/"+e.Repo) + "#" + strconv.Itoa(e.Number)
|
||||
}
|
||||
|
||||
// wantForge says the forge's open pull requests are to be read at the next look: a ready mark may have been made.
|
||||
func (h *Holder) wantForge() {
|
||||
h.mu.Lock()
|
||||
h.forgeWanted = true
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// takeForgeWanted answers whether the forge's open pull requests are to be read, and takes the want.
|
||||
func (h *Holder) takeForgeWanted() bool {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
wanted := h.forgeWanted
|
||||
h.forgeWanted = false
|
||||
return wanted
|
||||
}
|
||||
|
||||
// PullLabeled takes a label added to a pull request: the ready label asks for the mark to be read.
|
||||
func (h *Holder) PullLabeled(e ForgePullEvent) {
|
||||
if e.Label == ReadyLabel {
|
||||
h.wantForge()
|
||||
}
|
||||
}
|
||||
|
||||
// PullUnlabeled takes a label taken off a pull request: the ready label clears its mark, with no read.
|
||||
func (h *Holder) PullUnlabeled(e ForgePullEvent) {
|
||||
if e.Label != ReadyLabel {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if _, marked := h.marks[eventPullKey(e)]; marked {
|
||||
delete(h.marks, eventPullKey(e))
|
||||
h.Logf("[mesh-delivery] %s's ready label was taken off: its mark is cleared", eventPullKey(e))
|
||||
}
|
||||
}
|
||||
|
||||
// PullCommented takes a comment on a pull request: one that names a head to be ready at asks for the mark to be read.
|
||||
func (h *Holder) PullCommented(e ForgePullEvent) {
|
||||
if readyAtComment.MatchString(e.Comment) {
|
||||
h.wantForge()
|
||||
}
|
||||
}
|
||||
|
||||
// React takes every step the facts allow, after an event that can change what they are: the forge's open pull requests
|
||||
// read when a ready mark may have been made, then the asks of a look (look). Never concurrent with itself.
|
||||
func (h *Holder) React() {
|
||||
h.reactMu.Lock()
|
||||
defer h.reactMu.Unlock()
|
||||
if h.takeForgeWanted() {
|
||||
h.SeeOpenPulls()
|
||||
}
|
||||
h.look()
|
||||
}
|
||||
|
||||
// AskDecided takes the router's `decided` event for an ask this owner made: its warrant is applied to the deliveries
|
||||
// the ask covers, and what is said on their pull requests is said.
|
||||
func (h *Holder) AskDecided(id, state string, w *asks.Warrant) {
|
||||
h.mu.Lock()
|
||||
said := h.decided(id, state, w)
|
||||
h.mu.Unlock()
|
||||
for _, s := range said {
|
||||
s()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// **A ready mark, a verdict and an Approve are acted on as events** (novox/hq ADR 0406 §1, issue 975): the merge
|
||||
// is asked within the event that makes it possible, never at the next tick or the next minute.
|
||||
|
||||
const (
|
||||
labeledEvent = "gitea.forge.pull-request.labeled"
|
||||
unlabeledEvent = "gitea.forge.pull-request.unlabeled"
|
||||
commentedEvent = "gitea.forge.pull-request.commented"
|
||||
)
|
||||
|
||||
// hear is the runtime handing this owner one event, as handle takes it.
|
||||
func hear(t *testing.T, w *world, key string, body any) {
|
||||
t.Helper()
|
||||
raw, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := handle(w.h, stdio.Envelope{Key: key, Body: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func labeled(number int, label string) ForgePullEvent {
|
||||
return ForgePullEvent{Owner: "novox", Repo: "app", Number: number, HeadSHA: readyHead, Base: "main", Label: label,
|
||||
Labels: []string{label}}
|
||||
}
|
||||
|
||||
func commentedReady(number int, head string) ForgePullEvent {
|
||||
return ForgePullEvent{Owner: "novox", Repo: "app", Number: number, Author: "reviewer", CommentID: 5,
|
||||
Comment: "ready at " + head + "\n\napproved: the tests are the change's"}
|
||||
}
|
||||
|
||||
// greenAnnounced is a pull request into main, announced and judged green, the operator not answering by himself.
|
||||
func greenAnnounced(t *testing.T) *world {
|
||||
w := newWorld(t)
|
||||
w.op.auto = false
|
||||
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
|
||||
w.h.Checked(judgedAt("novox/app", 7, readyHead, "pass", "t1t1t1t1t1t1"))
|
||||
return w
|
||||
}
|
||||
|
||||
func TestALabelAndAReadyAtCommentAskTheOperatorAtOnceWithNoTick(t *testing.T) {
|
||||
w := greenAnnounced(t)
|
||||
// The label first: the forge reads it without a comment naming a head yet. Nothing is asked.
|
||||
w.forge.open = []OpenPull{markedPull("novox/app", 7, readyHead, "")}
|
||||
hear(t, w, labeledEvent, labeled(7, ReadyLabel))
|
||||
if w.op.count() != 0 || len(w.forge.merges) != 0 {
|
||||
t.Fatalf("a label with no head named was acted on: %d asks, merges %v", w.op.count(), w.forge.merges)
|
||||
}
|
||||
// The comment naming the head: the forge now reads the mark whole, and the ask is made within this event.
|
||||
w.forge.open = []OpenPull{markedPull("novox/app", 7, readyHead, readyHead)}
|
||||
hear(t, w, commentedEvent, commentedReady(7, readyHead))
|
||||
if w.op.count() != 1 {
|
||||
t.Fatalf("the comment that completes the mark asked the operator %d times, wanted once, with no tick", w.op.count())
|
||||
}
|
||||
if d := w.delivery(IDOf("novox/app", readyHead)); d.Ready == nil || d.Ready.By != "reviewer" || d.Ready.AskID == "" {
|
||||
t.Fatalf("the mark is not kept on the delivery: %+v", d.Ready)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACommentThatIsNotAReadyMarkReadsNothingFromTheForge(t *testing.T) {
|
||||
w := greenAnnounced(t)
|
||||
before := w.forge.openCalls
|
||||
c := commentedReady(7, readyHead)
|
||||
c.Comment = "looks fine to me, though I would not say ready at all"
|
||||
hear(t, w, commentedEvent, c)
|
||||
hear(t, w, labeledEvent, labeled(7, "kind/bug"))
|
||||
if w.forge.openCalls != before {
|
||||
t.Fatalf("the forge's open pull requests were read %d times for events that are no ready mark", w.forge.openCalls-before)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheReadyLabelTakenOffClearsTheMarkAtOnce(t *testing.T) {
|
||||
w := greenAnnounced(t)
|
||||
w.forge.open = []OpenPull{markedPull("novox/app", 7, readyHead, readyHead)}
|
||||
hear(t, w, labeledEvent, labeled(7, ReadyLabel))
|
||||
w.h.mu.Lock()
|
||||
marks := len(w.h.marks)
|
||||
w.h.mu.Unlock()
|
||||
if marks != 1 {
|
||||
t.Fatalf("the label did not make a mark: %d", marks)
|
||||
}
|
||||
asked := w.op.count()
|
||||
// The forge no longer shows the label; the event alone says so, and the forge is not read for it.
|
||||
reads := w.forge.openCalls
|
||||
w.forge.open = nil
|
||||
hear(t, w, unlabeledEvent, labeled(7, ReadyLabel))
|
||||
w.h.mu.Lock()
|
||||
marks = len(w.h.marks)
|
||||
w.h.mu.Unlock()
|
||||
if marks != 0 || w.forge.openCalls != reads || w.op.count() != asked {
|
||||
t.Fatalf("the mark was not cleared by the event alone: marks %d, reads %d, asks %d", marks, w.forge.openCalls-reads, w.op.count()-asked)
|
||||
}
|
||||
}
|
||||
|
||||
// approve is the operator's Approve of the one open ask, heard as the router's `decided` event.
|
||||
func approve(w *world) {
|
||||
open := w.op.open()
|
||||
w.op.answer(open[0], OptionApprove)
|
||||
state, warrant, _ := w.op.Where(open[0])
|
||||
w.h.AskDecided(open[0], state, warrant)
|
||||
w.h.React()
|
||||
}
|
||||
|
||||
func TestAnApproveHeardAsAnEventAsksTheMergeAtOnce(t *testing.T) {
|
||||
w := greenAnnounced(t)
|
||||
w.forge.open = []OpenPull{markedPull("novox/app", 7, readyHead, readyHead)}
|
||||
hear(t, w, commentedEvent, commentedReady(7, readyHead))
|
||||
open := w.op.open()
|
||||
if len(open) != 1 || len(w.forge.merges) != 0 {
|
||||
t.Fatalf("asks %v, merges %v", open, w.forge.merges)
|
||||
}
|
||||
approve(w)
|
||||
if len(w.forge.merges) != 1 || w.forge.merges[0] != "novox/app#7@"+readyHead {
|
||||
t.Fatalf("the Approve, heard as an event, merged %v; wanted once at the marked head", w.forge.merges)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPassingVerdictAsksTheMergeAtOnceEvenJustAfterARefusal(t *testing.T) {
|
||||
w := newWorld(t)
|
||||
id := IDOf("novox/app", readyHead)
|
||||
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
|
||||
w.h.Checked(judgedAt("novox/app", 7, readyHead, "pass", "t1t1t1t1t1t1"))
|
||||
// Another merge moves the trunk: the verdict is out of date and the merge tool refuses, asking the check again.
|
||||
w.h.PullMerged(merged("novox/app", 9, "9999999999999999", "t2t2t2t2t2t2"))
|
||||
w.forge.refuse = errAskedAgain
|
||||
w.op.auto = false
|
||||
w.forge.open = []OpenPull{markedPull("novox/app", 7, readyHead, readyHead)}
|
||||
hear(t, w, commentedEvent, commentedReady(7, readyHead))
|
||||
approve(w)
|
||||
if len(w.forge.merges) != 1 {
|
||||
t.Fatalf("the merge tool was asked %v; wanted once to judge it again", w.forge.merges)
|
||||
}
|
||||
// The verdict against the trunk's head passes: merged within the verdict's own event, no minute waited.
|
||||
w.forge.refuse = nil
|
||||
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
|
||||
raw, _ := json.Marshal(judgedAt("novox/app", 7, readyHead, "pass", "t2t2t2t2t2t2"))
|
||||
if err := handle(w.h, stdio.Envelope{Key: ControllerSeat + ".checked", Body: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(w.forge.merges) != 2 || w.delivery(id).Ready.Merged == nil {
|
||||
t.Fatalf("the passing verdict did not ask the merge at once: %v %+v", w.forge.merges, w.delivery(id).Ready)
|
||||
}
|
||||
}
|
||||
|
||||
var errAskedAgain = &askedAgainError{}
|
||||
|
||||
type askedAgainError struct{}
|
||||
|
||||
func (*askedAgainError) Error() string {
|
||||
return "the forge refused gitea_merge_pull_request: The check of head merged into trunk was asked"
|
||||
}
|
||||
@@ -399,8 +399,9 @@ type fakeForge struct {
|
||||
containsDown bool
|
||||
containsAsks []string
|
||||
// open is what OpenPulls answers; openDown a forge that cannot say.
|
||||
open []OpenPull
|
||||
openDown bool
|
||||
open []OpenPull
|
||||
openDown bool
|
||||
openCalls int
|
||||
// merges, comments and unlabels are what was asked of the forge for ready marks (novox/hq issue 722).
|
||||
merges, comments, unlabels []string
|
||||
refuse error
|
||||
@@ -412,6 +413,7 @@ type fakeForge struct {
|
||||
func (f *fakeForge) OpenPulls() ([]OpenPull, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.openCalls++
|
||||
if f.openDown {
|
||||
return nil, errors.New("the forge is away")
|
||||
}
|
||||
|
||||
@@ -84,6 +84,10 @@ type Holder struct {
|
||||
tried owedTries
|
||||
// marks are the open pull requests read with a ready mark, by owner/repo#number (novox/hq issue 722, ready.go).
|
||||
marks map[string]*marked
|
||||
// forgeWanted is whether the forge's open pull requests are to be read at the next React: a ready mark may have been
|
||||
// made (events.go). reactMu keeps one React at a time.
|
||||
forgeWanted bool
|
||||
reactMu sync.Mutex
|
||||
}
|
||||
|
||||
// owedTries is how many owed effects were tried since this owner started, and when the last was: three numbers
|
||||
@@ -509,6 +513,7 @@ func (h *Holder) PullMerged(p PullEvent) {
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
delete(h.marks, strings.ToLower(p.Owner+"/"+p.Repo)+fmt.Sprintf("#%d", p.Number)) // a merged pull request keeps no mark
|
||||
var d *Delivery
|
||||
if p.HeadSHA != "" {
|
||||
d = h.deliveries[IDOf(p.repository(), p.HeadSHA)]
|
||||
@@ -667,6 +672,7 @@ func verdictOfState(s string) string {
|
||||
// go of it within the week anyway.
|
||||
func (h *Holder) PullClosed(p PullEvent) {
|
||||
h.mu.Lock()
|
||||
delete(h.marks, strings.ToLower(p.Owner+"/"+p.Repo)+fmt.Sprintf("#%d", p.Number)) // a closed pull request keeps no mark
|
||||
for _, d := range h.deliveries {
|
||||
if d.Repository == p.repository() && d.Number == p.Number && !d.State.Final() && d.MergedAs == "" {
|
||||
d.ClosedUnmerged = true
|
||||
@@ -736,6 +742,9 @@ func (h *Holder) Checked(c CheckedEvent) {
|
||||
return
|
||||
}
|
||||
g.Check.Verdict, g.Check.Summary = c.Verdict, c.Summary
|
||||
for _, m := range h.membersOf(g) {
|
||||
h.mergeAgainNow(m) // the group's composed check answered: its members' merges are asked at once
|
||||
}
|
||||
// Each member's head and the trunk commit it was merged into (novox/hq ADR 0323 §6).
|
||||
for _, m := range c.Members {
|
||||
for id := range g.Check.Commits {
|
||||
@@ -796,6 +805,7 @@ func (h *Holder) Checked(c CheckedEvent) {
|
||||
v.Gate = "error"
|
||||
}
|
||||
d.Check, d.AskedAgain = v, nil
|
||||
h.mergeAgainNow(d) // a verdict taken: the merge is asked at once, not a minute after a refusal
|
||||
if c.Plan != nil {
|
||||
d.Plan = c.Plan
|
||||
}
|
||||
@@ -815,6 +825,11 @@ func (h *Holder) WalkMoved(w Walk) {
|
||||
}
|
||||
|
||||
func (h *Holder) walkMoved(w Walk) {
|
||||
for _, d := range h.deliveries {
|
||||
if d.Group != "" {
|
||||
h.mergeAgainNow(d) // a walk moved: a group member's turn to merge may have come, asked at once
|
||||
}
|
||||
}
|
||||
if w.Release != nil {
|
||||
h.creditBacklog(w)
|
||||
return
|
||||
@@ -1055,9 +1070,12 @@ func (h *Holder) Reconcile() error {
|
||||
|
||||
// --- the tick: groups, turns, adoption --------------------------------------------------------------------
|
||||
|
||||
// Tick takes every step the facts allow that needs another's word: a group's order and composed check, a
|
||||
// Tick is React, for the tick that still runs beside the events.
|
||||
func (h *Holder) Tick() { h.React() }
|
||||
|
||||
// look takes every step the facts allow that needs another's word: a group's order and composed check, a
|
||||
// delivery's turn, a walk with no delivery made one. The asking happens outside the lock.
|
||||
func (h *Holder) Tick() {
|
||||
func (h *Holder) look() {
|
||||
for _, ask := range h.plan() {
|
||||
ask()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ func (l *listening) set(s string) { l.mu.Lock(); l.now = s; l.mu.Unlock() }
|
||||
func (l *listening) get() string { l.mu.Lock(); defer l.mu.Unlock(); return l.now }
|
||||
|
||||
// The events this owner follows: the forge's pull requests, and the controller's verdicts and walks.
|
||||
// The forge's label and comment events say a ready mark changed (events.go).
|
||||
var followed = []string{"gitea.pull.updated", "gitea.pull.merged", "gitea.pull.closed",
|
||||
"gitea.forge.pull-request.labeled", "gitea.forge.pull-request.unlabeled", "gitea.forge.pull-request.commented",
|
||||
ControllerSeat + ".checked", ControllerSeat + ".plan-moved"}
|
||||
|
||||
func main() {
|
||||
@@ -104,9 +106,15 @@ func run(h *Holder, l *listening) {
|
||||
logf("[mesh-delivery] listening: %s", strings.Join(followed, ", "))
|
||||
}
|
||||
|
||||
// handle takes one event. An event that cannot be read is said and acknowledged: read again it would fail
|
||||
// again, and the controller's walks are read back by comparison anyway.
|
||||
// handle takes one event, then takes every step the event makes possible (React). An event that cannot be read is
|
||||
// said and acknowledged: read again it would fail again, and the controller's walks are read back by comparison anyway.
|
||||
func handle(h *Holder, e stdio.Envelope) error {
|
||||
taken := take(h, e)
|
||||
h.React()
|
||||
return taken
|
||||
}
|
||||
|
||||
func take(h *Holder, e stdio.Envelope) error {
|
||||
switch e.Key {
|
||||
case "gitea.pull.updated", "gitea.pull.merged", "gitea.pull.closed":
|
||||
var p PullEvent
|
||||
@@ -122,6 +130,20 @@ func handle(h *Holder, e stdio.Envelope) error {
|
||||
default:
|
||||
h.PullClosed(p)
|
||||
}
|
||||
case "gitea.forge.pull-request.labeled", "gitea.forge.pull-request.unlabeled", "gitea.forge.pull-request.commented":
|
||||
var p ForgePullEvent
|
||||
if err := json.Unmarshal(e.Body, &p); err != nil {
|
||||
h.Refuse(fmt.Errorf("%s is not readable: %v", e.Key, err))
|
||||
return nil
|
||||
}
|
||||
switch e.Key {
|
||||
case "gitea.forge.pull-request.labeled":
|
||||
h.PullLabeled(p)
|
||||
case "gitea.forge.pull-request.unlabeled":
|
||||
h.PullUnlabeled(p)
|
||||
default:
|
||||
h.PullCommented(p)
|
||||
}
|
||||
case ControllerSeat + ".checked":
|
||||
var c CheckedEvent
|
||||
if err := json.Unmarshal(e.Body, &c); err != nil {
|
||||
|
||||
@@ -65,6 +65,8 @@ type ReadyRun struct {
|
||||
Tries int `json:"tries,omitempty"`
|
||||
LastTry time.Time `json:"last_try"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
// merging is whether a merge ask is on its way, outside the lock: not asked twice meanwhile. Not kept.
|
||||
merging bool
|
||||
// RefusedSince is when the merge tool began refusing it without a break.
|
||||
RefusedSince *time.Time `json:"refused_since,omitempty"`
|
||||
// Merged is when the merge tool merged it.
|
||||
@@ -255,11 +257,11 @@ func (h *Holder) readyAsks(now time.Time) []func() {
|
||||
if approvalAsk != nil {
|
||||
asks = append(asks, approvalAsk)
|
||||
}
|
||||
if waits != "" || (!r.LastTry.IsZero() && now.Sub(r.LastTry) < readyTryEvery) {
|
||||
if waits != "" || r.merging || (!r.LastTry.IsZero() && now.Sub(r.LastTry) < readyTryEvery) {
|
||||
continue
|
||||
}
|
||||
r.Tries++
|
||||
r.LastTry = now
|
||||
r.LastTry, r.merging = now, true
|
||||
h.keep(d)
|
||||
asks = append(asks, h.askMerge(d.ID, p, mark.Head))
|
||||
}
|
||||
@@ -268,6 +270,14 @@ func (h *Holder) readyAsks(now time.Time) []func() {
|
||||
return asks
|
||||
}
|
||||
|
||||
// mergeAgainNow lets a marked delivery's merge be asked at once: something that decides whether it may merge happened (a
|
||||
// verdict, an Approve, a group's turn), so the wait after a refusal does not hold it. Under the lock.
|
||||
func (h *Holder) mergeAgainNow(d *Delivery) {
|
||||
if d != nil && d.Ready != nil && d.Ready.Merged == nil {
|
||||
d.Ready.LastTry = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// readyWaits is what a marked delivery's merge waits on; empty when it is asked now. A ready delivery is asked; so is
|
||||
// a proposed one whose verdict went out of date and whose check nobody asked again — the merge tool asks it.
|
||||
func (h *Holder) readyWaits(d *Delivery) string {
|
||||
@@ -325,6 +335,7 @@ func (h *Holder) askMerge(id string, p OpenPull, head string) func() {
|
||||
return
|
||||
}
|
||||
r, now := d.Ready, h.Now()
|
||||
r.merging = false
|
||||
if err == nil {
|
||||
r.Merged, r.Answer, r.RefusedSince = &now, "merged", nil
|
||||
h.Logf("[mesh-delivery] %s merged on its ready mark at %s, its check passed against its trunk's head", id,
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"gitea.pull.updated",
|
||||
"gitea.pull.merged",
|
||||
"gitea.pull.closed",
|
||||
"gitea.forge.pull-request.labeled",
|
||||
"gitea.forge.pull-request.unlabeled",
|
||||
"gitea.forge.pull-request.commented",
|
||||
"mesh-controller.checked",
|
||||
"mesh-controller.plan-moved"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user