subtree(3p/gerrit-queue): Vendor at commit '24f5a642
'
Imported from github/tvlfyi/gerrit-queue, originally from github/tweag/gerrit-queue but that upstream is unmaintained. git-subtree-dir: third_party/gerrit-queue git-subtree-mainline:ff10b7ab83
git-subtree-split:24f5a642af
Change-Id: I307cc38185ab9e25eb102c95096298a150ae13a2
This commit is contained in:
commit
59f97332b3
21 changed files with 1641 additions and 0 deletions
212
third_party/gerrit-queue/submitqueue/runner.go
vendored
Normal file
212
third_party/gerrit-queue/submitqueue/runner.go
vendored
Normal file
|
@ -0,0 +1,212 @@
|
|||
package submitqueue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/apex/log"
|
||||
|
||||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
)
|
||||
|
||||
// Runner is a struct existing across the lifetime of a single run of the submit queue
|
||||
// it contains a mutex to avoid being run multiple times.
|
||||
// In fact, it even cancels runs while another one is still in progress.
|
||||
// It contains a Gerrit object facilitating access, a log object, the configured submit queue tag
|
||||
// and a `wipSerie` (only populated if waiting for a rebase)
|
||||
type Runner struct {
|
||||
mut sync.Mutex
|
||||
currentlyRunning bool
|
||||
wipSerie *gerrit.Serie
|
||||
logger *log.Logger
|
||||
gerrit *gerrit.Client
|
||||
}
|
||||
|
||||
// NewRunner creates a new Runner struct
|
||||
func NewRunner(logger *log.Logger, gerrit *gerrit.Client) *Runner {
|
||||
return &Runner{
|
||||
logger: logger,
|
||||
gerrit: gerrit,
|
||||
}
|
||||
}
|
||||
|
||||
// isAutoSubmittable determines if something could be autosubmitted, potentially requiring a rebase
|
||||
// for this, it needs to:
|
||||
// * have the "Autosubmit" label set to +1
|
||||
// * have gerrit's 'submittable' field set to true
|
||||
// it doesn't check if the series is rebased on HEAD
|
||||
func (r *Runner) isAutoSubmittable(s *gerrit.Serie) bool {
|
||||
for _, c := range s.ChangeSets {
|
||||
if c.Submittable != true || !c.IsAutosubmit() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCurrentlyRunning returns true if the runner is currently running
|
||||
func (r *Runner) IsCurrentlyRunning() bool {
|
||||
return r.currentlyRunning
|
||||
}
|
||||
|
||||
// GetWIPSerie returns the current wipSerie, if any, nil otherwiese
|
||||
// Acquires a lock, so check with IsCurrentlyRunning first
|
||||
func (r *Runner) GetWIPSerie() *gerrit.Serie {
|
||||
r.mut.Lock()
|
||||
defer func() {
|
||||
r.mut.Unlock()
|
||||
}()
|
||||
return r.wipSerie
|
||||
}
|
||||
|
||||
// Trigger gets triggered periodically
|
||||
func (r *Runner) Trigger(fetchOnly bool) error {
|
||||
// TODO: If CI fails, remove the auto-submit labels => rules.pl
|
||||
// Only one trigger can run at the same time
|
||||
r.mut.Lock()
|
||||
if r.currentlyRunning {
|
||||
return fmt.Errorf("Already running, skipping")
|
||||
}
|
||||
r.currentlyRunning = true
|
||||
r.mut.Unlock()
|
||||
defer func() {
|
||||
r.mut.Lock()
|
||||
r.currentlyRunning = false
|
||||
r.mut.Unlock()
|
||||
}()
|
||||
|
||||
// Prepare the work by creating a local cache of gerrit state
|
||||
err := r.gerrit.Refresh()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// early return if we only want to fetch
|
||||
if fetchOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.wipSerie != nil {
|
||||
// refresh wipSerie with how it looks like in gerrit now
|
||||
wipSerie := r.gerrit.FindSerie(func(s *gerrit.Serie) bool {
|
||||
// the new wipSerie needs to have the same number of changesets
|
||||
if len(r.wipSerie.ChangeSets) != len(s.ChangeSets) {
|
||||
return false
|
||||
}
|
||||
// … and the same ChangeIDs.
|
||||
for idx, c := range s.ChangeSets {
|
||||
if r.wipSerie.ChangeSets[idx].ChangeID != c.ChangeID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if wipSerie == nil {
|
||||
r.logger.WithField("wipSerie", r.wipSerie).Warn("wipSerie has disappeared")
|
||||
r.wipSerie = nil
|
||||
} else {
|
||||
r.wipSerie = wipSerie
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// initialize logger
|
||||
r.logger.Info("Running")
|
||||
if r.wipSerie != nil {
|
||||
// if we have a wipSerie
|
||||
l := r.logger.WithField("wipSerie", r.wipSerie)
|
||||
l.Info("Checking wipSerie")
|
||||
|
||||
// discard wipSerie not rebased on HEAD
|
||||
// we rebase them at the end of the loop, so this means master advanced without going through the submit queue
|
||||
if !r.gerrit.SerieIsRebasedOnHEAD(r.wipSerie) {
|
||||
l.Warnf("HEAD has moved to {} while still waiting for wipSerie, discarding it", r.gerrit.GetHEAD())
|
||||
r.wipSerie = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// we now need to check CI feedback:
|
||||
// wipSerie might have failed CI in the meantime
|
||||
for _, c := range r.wipSerie.ChangeSets {
|
||||
if c.Verified < 0 {
|
||||
l.WithField("failingChangeset", c).Warnf("wipSerie failed CI in the meantime, discarding.")
|
||||
r.wipSerie = nil
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// it might still be waiting for CI
|
||||
for _, c := range r.wipSerie.ChangeSets {
|
||||
if c.Verified == 0 {
|
||||
l.WithField("pendingChangeset", c).Warnf("still waiting for CI feedback in wipSerie, going back to sleep.")
|
||||
// break the loop, take a look at it at the next trigger.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// it might be autosubmittable
|
||||
if r.isAutoSubmittable(r.wipSerie) {
|
||||
l.Infof("submitting wipSerie")
|
||||
// if the WIP changeset is ready (auto submittable and rebased on HEAD), submit
|
||||
for _, changeset := range r.wipSerie.ChangeSets {
|
||||
_, err := r.gerrit.SubmitChangeset(changeset)
|
||||
if err != nil {
|
||||
l.WithField("changeset", changeset).Error("error submitting changeset")
|
||||
r.wipSerie = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.wipSerie = nil
|
||||
} else {
|
||||
// should never be reached?!
|
||||
log.Warnf("reached branch we should never reach")
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("Looking for series ready to submit")
|
||||
// Find serie, that:
|
||||
// * has the auto-submit label
|
||||
// * has +2 review
|
||||
// * has +1 CI
|
||||
// * is rebased on master
|
||||
serie := r.gerrit.FindSerie(func(s *gerrit.Serie) bool {
|
||||
return r.isAutoSubmittable(s) && s.ChangeSets[0].ParentCommitIDs[0] == r.gerrit.GetHEAD()
|
||||
})
|
||||
if serie != nil {
|
||||
r.logger.WithField("serie", serie).Info("Found serie to submit without necessary rebase")
|
||||
r.wipSerie = serie
|
||||
continue
|
||||
}
|
||||
|
||||
// Find serie, that:
|
||||
// * has the auto-submit label
|
||||
// * has +2 review
|
||||
// * has +1 CI
|
||||
// * is NOT rebased on master
|
||||
serie = r.gerrit.FindSerie(r.isAutoSubmittable)
|
||||
if serie == nil {
|
||||
r.logger.Info("no more submittable series found, going back to sleep.")
|
||||
break
|
||||
}
|
||||
|
||||
l := r.logger.WithField("serie", serie)
|
||||
l.Info("found serie, which needs a rebase")
|
||||
// TODO: move into Client.RebaseSeries function
|
||||
head := r.gerrit.GetHEAD()
|
||||
for _, changeset := range serie.ChangeSets {
|
||||
changeset, err := r.gerrit.RebaseChangeset(changeset, head)
|
||||
if err != nil {
|
||||
l.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
head = changeset.CommitID
|
||||
}
|
||||
// we don't need to care about updating the rebased changesets or getting the updated HEAD,
|
||||
// as we'll refetch it on the beginning of the next trigger anyways
|
||||
r.wipSerie = serie
|
||||
break
|
||||
}
|
||||
|
||||
r.logger.Info("Run complete")
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue