mesh/delivery merged: source.main moved to novox/mesh-catalog@27a7757b970e
mesh/check pass 24b244bc88dc on 27a7757b970e | carried from 6b6d5cba7195 | unit 2046 run, 2327 kept, 6 skipped | integration 2066 run, 2346 kept, 10 s…
car-hunter #2 was approved and its commit status then refused 403. Before the ask the gate reads merge.writes for each repository it covers; where the merging account cannot write, nothing is asked, the delivery waits on 'the forge account cannot write <repo>: gitea_grant_write' and a condition is raised. gitea.repo.writable reads it again and asks an approved, refused merge of it again.
116 lines
4.4 KiB
Go
116 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// **The operator is asked only for a merge the forge account can carry out** (novox/hq issue 1945). car-hunter #2, a
|
|
// repository of a user, was approved, and its first act after the Approve, the commit status, was refused 403: the mesh's
|
|
// merging account (the gitea module's tools, mesh-tools) only read that repository. Before the gate asks the operator's
|
|
// Approve it reads, through the forge holder's gitea_commit_statuses, whether that account writes each repository the
|
|
// ask covers (merge.writes), as the ask is about to be written. Where it does not, nothing is asked: the delivery waits,
|
|
// saying "the forge account cannot write <repo>: gitea_grant_write", and the gate raises it as a condition at once.
|
|
//
|
|
// A repository not written is kept so, and the ask is not composed again, until the gitea module's event
|
|
// gitea.repo.writable (its grant, read back from the forge) or, as a safety net, writeReadLasts after the read. A forge
|
|
// that does not say merge.writes (an older gitea module) is taken as writing: the merge says its own refusal, as before.
|
|
|
|
// writeReadLasts is how long a read of whether the forge account writes a repository holds.
|
|
const writeReadLasts = 10 * time.Minute
|
|
|
|
// writableEvent is the gitea module's event of an account given write on a repository.
|
|
const writableEvent = "gitea.repo.writable"
|
|
|
|
// writeRead is what the forge last said of the merging account on one repository it cannot write.
|
|
type writeRead struct {
|
|
says string
|
|
at time.Time
|
|
}
|
|
|
|
// cannotWriteSays is what a delivery waits on when the forge account cannot write its repository.
|
|
func cannotWriteSays(repository string) string {
|
|
return "the forge account cannot write " + repository + ": gitea_grant_write"
|
|
}
|
|
|
|
// writeWaits is what an ask about to be made waits on: a repository it covers that the forge account was read, within
|
|
// writeReadLasts, not to write. Under the lock.
|
|
func (h *Holder) writeWaits(d *Delivery, members []*Delivery, now time.Time) string {
|
|
r := d.Ready
|
|
for _, m := range members {
|
|
repo := strings.ToLower(m.Repository)
|
|
w := h.writeReads[repo]
|
|
if w == nil {
|
|
continue
|
|
}
|
|
if !now.Before(w.at.Add(writeReadLasts)) {
|
|
delete(h.writeReads, repo) // read again at the ask
|
|
continue
|
|
}
|
|
if r.CannotWrite != w.says {
|
|
r.CannotWrite, r.CannotWriteSince = w.says, &now
|
|
h.keep(d)
|
|
}
|
|
h.lookAt(w.at.Add(writeReadLasts), "a repository the forge account could not write, read again")
|
|
return w.says
|
|
}
|
|
if r.CannotWrite != "" {
|
|
r.CannotWrite, r.CannotWriteSince = "", nil
|
|
h.keep(d)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// cannotWrite reads, at the ask, whether the forge account writes each repository the ask covers, and answers what the
|
|
// first one it cannot write says; a read that fails says nothing (the merge says its own refusal, as before). Outside the
|
|
// lock; a repository not written is kept, under the lock.
|
|
func (h *Holder) cannotWrite(reads []memberRead) string {
|
|
for _, m := range reads {
|
|
fc, err := h.Forge.Statuses(m.owner, m.repo, m.number, m.head, m.base)
|
|
if err != nil || fc == nil || fc.Merge.Writes == nil || *fc.Merge.Writes {
|
|
continue
|
|
}
|
|
repo := strings.ToLower(m.owner + "/" + m.repo)
|
|
says := fc.Merge.CannotWrite
|
|
if says == "" {
|
|
says = cannotWriteSays(repo)
|
|
}
|
|
h.mu.Lock()
|
|
if h.writeReads == nil {
|
|
h.writeReads = map[string]*writeRead{}
|
|
}
|
|
h.writeReads[repo] = &writeRead{says: says, at: h.Now()}
|
|
h.mu.Unlock()
|
|
return says
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// WriteGranted takes the gitea module's gitea.repo.writable: the repository's read is dropped, so the next look reads it
|
|
// again, and an approved merge of it that the forge refused is asked again, its cause gone.
|
|
func (h *Holder) WriteGranted(body json.RawMessage) error {
|
|
var e struct {
|
|
Owner string `json:"owner"`
|
|
Repo string `json:"repo"`
|
|
}
|
|
if err := json.Unmarshal(body, &e); err != nil || e.Owner == "" || e.Repo == "" {
|
|
return fmt.Errorf("%s names no repository: %s", writableEvent, string(body))
|
|
}
|
|
repo := strings.ToLower(e.Owner + "/" + e.Repo)
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
delete(h.writeReads, repo)
|
|
for _, d := range h.deliveries {
|
|
if strings.ToLower(d.Repository) != repo || d.Ready == nil || d.Ready.Approved == nil || d.Ready.Merged != nil {
|
|
continue
|
|
}
|
|
d.Ready.Repeats = 0
|
|
h.mergeAgainNow(d)
|
|
h.keep(d)
|
|
}
|
|
h.Logf("[mesh-delivery] the forge account was given write on %s: it is read again before an ask, and an approved merge of it is asked again", repo)
|
|
return nil
|
|
}
|