Use Runner
This revamps code quite a bit. Series handling has been moved into the gerrit client, it also handles caching. The Runner logic itself has been greatly simplified. The runner logic has been moved into the runner.go, submitqueue.go is gone. The "per-run result object" concept has been dropped - we instead just use annotated logs. Also, we switched to apex/log
This commit is contained in:
parent
7bafef7a84
commit
04a24a0c60
14 changed files with 486 additions and 537 deletions
|
@ -13,9 +13,9 @@ import (
|
|||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
_ "github.com/tweag/gerrit-queue/statik" // register static assets
|
||||
"github.com/tweag/gerrit-queue/submitqueue"
|
||||
)
|
||||
|
||||
//TODO: log last update
|
||||
"github.com/apex/log/handlers/memory"
|
||||
)
|
||||
|
||||
//loadTemplate loads a list of templates, relative to the statikFS root, and a FuncMap, and returns a template object
|
||||
func loadTemplate(templateNames []string, funcMap template.FuncMap) (*template.Template, error) {
|
||||
|
@ -47,46 +47,48 @@ func loadTemplate(templateNames []string, funcMap template.FuncMap) (*template.T
|
|||
return tmpl, nil
|
||||
}
|
||||
|
||||
// MakeFrontend configures the router and returns a new Frontend struct
|
||||
func MakeFrontend(runner *submitqueue.Runner) http.Handler {
|
||||
// MakeFrontend returns a http.Handler
|
||||
func MakeFrontend(memoryHandler *memory.Handler, gerritClient *gerrit.Client, runner *submitqueue.Runner) http.Handler {
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/submit-queue.json", func(c *gin.Context) {
|
||||
submitQueue, _, _ := runner.GetState()
|
||||
c.JSON(http.StatusOK, submitQueue)
|
||||
})
|
||||
projectName := gerritClient.GetProjectName()
|
||||
branchName := gerritClient.GetBranchName()
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
submitQueue, currentlyRunning, results := runner.GetState()
|
||||
var wipSerie *gerrit.Serie = nil
|
||||
HEAD := ""
|
||||
currentlyRunning := runner.IsCurrentlyRunning()
|
||||
|
||||
// don't trigger operations requiring a lock
|
||||
if !currentlyRunning {
|
||||
wipSerie = runner.GetWIPSerie()
|
||||
HEAD = gerritClient.GetHEAD()
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"isAutoSubmittable": func(serie *submitqueue.Serie) bool {
|
||||
return submitQueue.IsAutoSubmittable(serie)
|
||||
},
|
||||
"changesetURL": func(changeset *gerrit.Changeset) string {
|
||||
return submitQueue.GetChangesetURL(changeset)
|
||||
return gerritClient.GetChangesetURL(changeset)
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := template.Must(loadTemplate([]string{
|
||||
"submit-queue.tmpl.html",
|
||||
"series.tmpl.html",
|
||||
"serie.tmpl.html",
|
||||
"changeset.tmpl.html",
|
||||
}, funcMap))
|
||||
|
||||
tmpl.ExecuteTemplate(c.Writer, "submit-queue.tmpl.html", gin.H{
|
||||
// Config
|
||||
"projectName": submitQueue.ProjectName,
|
||||
"branchName": submitQueue.BranchName,
|
||||
"projectName": projectName,
|
||||
"branchName": branchName,
|
||||
|
||||
// State
|
||||
"currentlyRunning": currentlyRunning,
|
||||
"series": submitQueue.Series,
|
||||
"HEAD": submitQueue.HEAD,
|
||||
"wipSerie": wipSerie,
|
||||
"HEAD": HEAD,
|
||||
|
||||
// History
|
||||
"results": results,
|
||||
"memory": memoryHandler,
|
||||
})
|
||||
})
|
||||
return router
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
goGerrit "github.com/andygrunwald/go-gerrit"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/apex/log"
|
||||
)
|
||||
|
||||
// Changeset represents a single changeset
|
||||
|
@ -14,8 +14,8 @@ type Changeset struct {
|
|||
changeInfo *goGerrit.ChangeInfo
|
||||
ChangeID string
|
||||
Number int
|
||||
IsVerified bool
|
||||
IsCodeReviewed bool
|
||||
Verified int
|
||||
CodeReviewed int
|
||||
HashTags []string
|
||||
CommitID string
|
||||
ParentCommitIDs []string
|
||||
|
@ -29,8 +29,8 @@ func MakeChangeset(changeInfo *goGerrit.ChangeInfo) *Changeset {
|
|||
changeInfo: changeInfo,
|
||||
ChangeID: changeInfo.ChangeID,
|
||||
Number: changeInfo.Number,
|
||||
IsVerified: isVerified(changeInfo),
|
||||
IsCodeReviewed: isCodeReviewed(changeInfo),
|
||||
Verified: labelInfoToInt(changeInfo.Labels["Verified"]),
|
||||
CodeReviewed: labelInfoToInt(changeInfo.Labels["Code-Review"]),
|
||||
HashTags: changeInfo.Hashtags,
|
||||
CommitID: changeInfo.CurrentRevision, // yes, this IS the commit ID.
|
||||
ParentCommitIDs: getParentCommitIDs(changeInfo),
|
||||
|
@ -39,12 +39,6 @@ func MakeChangeset(changeInfo *goGerrit.ChangeInfo) *Changeset {
|
|||
}
|
||||
}
|
||||
|
||||
// MakeMockChangeset creates a mock changeset
|
||||
// func MakeMockChangeset(isVerified, IsCodeReviewed bool, hashTags []string, commitID string, parentCommitIDs []string, ownerName, subject string) *Changeset {
|
||||
// //TODO impl
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// HasTag returns true if a Changeset has the given tag.
|
||||
func (c *Changeset) HasTag(tag string) bool {
|
||||
hashTags := c.HashTags
|
||||
|
@ -56,6 +50,18 @@ func (c *Changeset) HasTag(tag string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsVerified returns true if the changeset passed CI,
|
||||
// that's when somebody left the Approved (+1) on the "Verified" label
|
||||
func (c *Changeset) IsVerified() bool {
|
||||
return c.Verified == 1
|
||||
}
|
||||
|
||||
// IsCodeReviewed returns true if the changeset passed code review,
|
||||
// that's when somebody left the Recommended (+2) on the "Code-Review" label
|
||||
func (c *Changeset) IsCodeReviewed() bool {
|
||||
return c.CodeReviewed == 2
|
||||
}
|
||||
|
||||
func (c *Changeset) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("Changeset")
|
||||
|
@ -76,18 +82,21 @@ func FilterChangesets(changesets []*Changeset, f func(*Changeset) bool) []*Chang
|
|||
return newChangesets
|
||||
}
|
||||
|
||||
// isVerified returns true if the code passed CI,
|
||||
// that's when somebody left the Approved (+1) on the "Verified" label
|
||||
func isVerified(changeInfo *goGerrit.ChangeInfo) bool {
|
||||
labels := changeInfo.Labels
|
||||
return labels["Verified"].Approved.AccountID != 0
|
||||
}
|
||||
|
||||
// isCodeReviewed returns true if the code passed code review,
|
||||
// that's when somebody left the Recommended (+2) on the "Code-Review" label
|
||||
func isCodeReviewed(changeInfo *goGerrit.ChangeInfo) bool {
|
||||
labels := changeInfo.Labels
|
||||
return labels["Code-Review"].Recommended.AccountID != 0
|
||||
// labelInfoToInt converts a goGerrit.LabelInfo to -2…+2 int
|
||||
func labelInfoToInt(labelInfo goGerrit.LabelInfo) int {
|
||||
if labelInfo.Recommended.AccountID != 0 {
|
||||
return 2
|
||||
}
|
||||
if labelInfo.Approved.AccountID != 0 {
|
||||
return 1
|
||||
}
|
||||
if labelInfo.Disliked.AccountID != 0 {
|
||||
return -1
|
||||
}
|
||||
if labelInfo.Rejected.AccountID != 0 {
|
||||
return -2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// getParentCommitIDs returns the parent commit IDs of the goGerrit.ChangeInfo
|
||||
|
|
173
gerrit/client.go
173
gerrit/client.go
|
@ -1,35 +1,52 @@
|
|||
package gerrit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
goGerrit "github.com/andygrunwald/go-gerrit"
|
||||
"github.com/apex/log"
|
||||
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// passed to gerrit when retrieving changesets
|
||||
var additionalFields = []string{"LABELS", "CURRENT_REVISION", "CURRENT_COMMIT", "DETAILED_ACCOUNTS"}
|
||||
var additionalFields = []string{
|
||||
"LABELS",
|
||||
"CURRENT_REVISION",
|
||||
"CURRENT_COMMIT",
|
||||
"DETAILED_ACCOUNTS",
|
||||
}
|
||||
|
||||
// IClient defines the gerrit.Client interface
|
||||
type IClient interface {
|
||||
SearchChangesets(queryString string) (changesets []*Changeset, Error error)
|
||||
GetHEAD(projectName string, branchName string) (string, error)
|
||||
GetChangeset(changeID string) (*Changeset, error)
|
||||
Refresh() error
|
||||
GetHEAD() string
|
||||
GetBaseURL() string
|
||||
GetChangesetURL(changeset *Changeset) string
|
||||
SubmitChangeset(changeset *Changeset) (*Changeset, error)
|
||||
RebaseChangeset(changeset *Changeset, ref string) (*Changeset, error)
|
||||
RemoveTag(changeset *Changeset, tag string) (*Changeset, error)
|
||||
GetBaseURL() string
|
||||
ChangesetIsRebasedOnHEAD(changeset *Changeset) bool
|
||||
SerieIsRebasedOnHEAD(serie *Serie) bool
|
||||
FilterSeries(filter func(s *Serie) bool) []*Serie
|
||||
FindSerie(filter func(s *Serie) bool) *Serie
|
||||
}
|
||||
|
||||
var _ IClient = &Client{}
|
||||
|
||||
// Client provides some ways to interact with a gerrit instance
|
||||
type Client struct {
|
||||
client *goGerrit.Client
|
||||
baseURL string
|
||||
client *goGerrit.Client
|
||||
logger *log.Logger
|
||||
baseURL string
|
||||
projectName string
|
||||
branchName string
|
||||
series []*Serie
|
||||
head string
|
||||
}
|
||||
|
||||
// NewClient initializes a new gerrit client
|
||||
func NewClient(URL, username, password string) (*Client, error) {
|
||||
func NewClient(logger *log.Logger, URL, username, password, projectName, branchName string) (*Client, error) {
|
||||
urlParsed, err := url.Parse(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -43,17 +60,58 @@ func NewClient(URL, username, password string) (*Client, error) {
|
|||
return &Client{
|
||||
client: goGerritClient,
|
||||
baseURL: URL,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SearchChangesets fetches a list of changesets matching a passed query string
|
||||
func (gerrit *Client) SearchChangesets(queryString string) (changesets []*Changeset, Error error) {
|
||||
// refreshHEAD queries the commit ID of the selected project and branch
|
||||
func (c *Client) refreshHEAD() (string, error) {
|
||||
branchInfo, _, err := c.client.Projects.GetBranch(c.projectName, c.branchName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return branchInfo.Revision, nil
|
||||
}
|
||||
|
||||
// GetHEAD returns the internally stored HEAD
|
||||
func (c *Client) GetHEAD() string {
|
||||
return c.head
|
||||
}
|
||||
|
||||
// Refresh causes the client to refresh internal view of gerrit
|
||||
func (c *Client) Refresh() error {
|
||||
c.logger.Debug("refreshing from gerrit")
|
||||
HEAD, err := c.refreshHEAD()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.head = HEAD
|
||||
|
||||
var queryString = fmt.Sprintf("status:open project:%s branch:%s", c.projectName, c.branchName)
|
||||
c.logger.Debugf("fetching changesets: %s", queryString)
|
||||
changesets, err := c.fetchChangesets(queryString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Warnf("assembling series…")
|
||||
series, err := AssembleSeries(changesets, c.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
series = SortSeries(series)
|
||||
c.series = series
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchChangesets fetches a list of changesets matching a passed query string
|
||||
func (c *Client) fetchChangesets(queryString string) (changesets []*Changeset, Error error) {
|
||||
opt := &goGerrit.QueryChangeOptions{}
|
||||
opt.Query = []string{
|
||||
queryString,
|
||||
}
|
||||
opt.AdditionalFields = additionalFields //TODO: check DETAILED_ACCOUNTS is needed
|
||||
changes, _, err := gerrit.client.Changes.QueryChanges(opt)
|
||||
opt.AdditionalFields = additionalFields
|
||||
changes, _, err := c.client.Changes.QueryChanges(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -66,22 +124,13 @@ func (gerrit *Client) SearchChangesets(queryString string) (changesets []*Change
|
|||
return changesets, nil
|
||||
}
|
||||
|
||||
// GetHEAD returns the commit ID of a selected branch
|
||||
func (gerrit *Client) GetHEAD(projectName string, branchName string) (string, error) {
|
||||
branchInfo, _, err := gerrit.client.Projects.GetBranch(projectName, branchName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return branchInfo.Revision, nil
|
||||
}
|
||||
|
||||
// GetChangeset downloads an existing Changeset from gerrit, by its ID
|
||||
// fetchChangeset downloads an existing Changeset from gerrit, by its ID
|
||||
// Gerrit's API is a bit sparse, and only returns what you explicitly ask it
|
||||
// This is used to refresh an existing changeset with more data.
|
||||
func (gerrit *Client) GetChangeset(changeID string) (*Changeset, error) {
|
||||
func (c *Client) fetchChangeset(changeID string) (*Changeset, error) {
|
||||
opt := goGerrit.ChangeOptions{}
|
||||
opt.AdditionalFields = []string{"LABELS", "DETAILED_ACCOUNTS"}
|
||||
changeInfo, _, err := gerrit.client.Changes.GetChange(changeID, &opt)
|
||||
changeInfo, _, err := c.client.Changes.GetChange(changeID, &opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,28 +138,30 @@ func (gerrit *Client) GetChangeset(changeID string) (*Changeset, error) {
|
|||
}
|
||||
|
||||
// SubmitChangeset submits a given changeset, and returns a changeset afterwards.
|
||||
func (gerrit *Client) SubmitChangeset(changeset *Changeset) (*Changeset, error) {
|
||||
changeInfo, _, err := gerrit.client.Changes.SubmitChange(changeset.ChangeID, &goGerrit.SubmitInput{})
|
||||
// TODO: update HEAD
|
||||
func (c *Client) SubmitChangeset(changeset *Changeset) (*Changeset, error) {
|
||||
changeInfo, _, err := c.client.Changes.SubmitChange(changeset.ChangeID, &goGerrit.SubmitInput{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gerrit.GetChangeset(changeInfo.ChangeID)
|
||||
return c.fetchChangeset(changeInfo.ChangeID)
|
||||
}
|
||||
|
||||
// RebaseChangeset rebases a given changeset on top of a given ref
|
||||
func (gerrit *Client) RebaseChangeset(changeset *Changeset, ref string) (*Changeset, error) {
|
||||
changeInfo, _, err := gerrit.client.Changes.RebaseChange(changeset.ChangeID, &goGerrit.RebaseInput{
|
||||
// TODO: update HEAD
|
||||
func (c *Client) RebaseChangeset(changeset *Changeset, ref string) (*Changeset, error) {
|
||||
changeInfo, _, err := c.client.Changes.RebaseChange(changeset.ChangeID, &goGerrit.RebaseInput{
|
||||
Base: ref,
|
||||
})
|
||||
if err != nil {
|
||||
return changeset, err
|
||||
}
|
||||
return gerrit.GetChangeset(changeInfo.ChangeID)
|
||||
return c.fetchChangeset(changeInfo.ChangeID)
|
||||
}
|
||||
|
||||
// RemoveTag removes the submit queue tag from a changeset and updates gerrit
|
||||
// we never add, that's something users should do in the GUI.
|
||||
func (gerrit *Client) RemoveTag(changeset *Changeset, tag string) (*Changeset, error) {
|
||||
func (c *Client) RemoveTag(changeset *Changeset, tag string) (*Changeset, error) {
|
||||
hashTags := changeset.HashTags
|
||||
newHashTags := []string{}
|
||||
for _, hashTag := range hashTags {
|
||||
|
@ -118,12 +169,66 @@ func (gerrit *Client) RemoveTag(changeset *Changeset, tag string) (*Changeset, e
|
|||
newHashTags = append(newHashTags, hashTag)
|
||||
}
|
||||
}
|
||||
// TODO: implement set hashtags api in go-gerrit and use here
|
||||
// TODO: implement setting hashtags api in go-gerrit and use here
|
||||
// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-hashtags
|
||||
return changeset, nil
|
||||
}
|
||||
|
||||
// GetBaseURL returns the gerrit base URL
|
||||
func (gerrit *Client) GetBaseURL() string {
|
||||
return gerrit.baseURL
|
||||
func (c *Client) GetBaseURL() string {
|
||||
return c.baseURL
|
||||
}
|
||||
|
||||
// GetProjectName returns the configured gerrit project name
|
||||
func (c *Client) GetProjectName() string {
|
||||
return c.projectName
|
||||
}
|
||||
|
||||
// GetBranchName returns the configured gerrit branch name
|
||||
func (c *Client) GetBranchName() string {
|
||||
return c.branchName
|
||||
}
|
||||
|
||||
// GetChangesetURL returns the URL to view a given changeset
|
||||
func (c *Client) GetChangesetURL(changeset *Changeset) string {
|
||||
return fmt.Sprintf("%s/c/%s/+/%d", c.GetBaseURL(), c.projectName, changeset.Number)
|
||||
}
|
||||
|
||||
// ChangesetIsRebasedOnHEAD returns true if the changeset is rebased on the current HEAD
|
||||
func (c *Client) ChangesetIsRebasedOnHEAD(changeset *Changeset) bool {
|
||||
if len(changeset.ParentCommitIDs) != 1 {
|
||||
return false
|
||||
}
|
||||
return changeset.ParentCommitIDs[0] == c.head
|
||||
}
|
||||
|
||||
// SerieIsRebasedOnHEAD returns true if the whole series is rebased on the current HEAD
|
||||
// this is already the case if the first changeset in the series is rebased on the current HEAD
|
||||
func (c *Client) SerieIsRebasedOnHEAD(serie *Serie) bool {
|
||||
// an empty serie should not exist
|
||||
if len(serie.ChangeSets) == 0 {
|
||||
return false
|
||||
}
|
||||
return c.ChangesetIsRebasedOnHEAD(serie.ChangeSets[0])
|
||||
}
|
||||
|
||||
// FilterSeries returns a subset of all Series, passing the given filter function
|
||||
func (c *Client) FilterSeries(filter func(s *Serie) bool) []*Serie {
|
||||
matchedSeries := []*Serie{}
|
||||
for _, serie := range c.series {
|
||||
if filter(serie) {
|
||||
matchedSeries = append(matchedSeries, serie)
|
||||
}
|
||||
}
|
||||
return matchedSeries
|
||||
}
|
||||
|
||||
// FindSerie returns the first serie that matches the filter, or nil if none was found
|
||||
func (c *Client) FindSerie(filter func(s *Serie) bool) *Serie {
|
||||
for _, serie := range c.series {
|
||||
if filter(serie) {
|
||||
return serie
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package submitqueue
|
||||
package gerrit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/apex/log"
|
||||
)
|
||||
|
||||
// Serie represents a list of successive changesets with an unbroken parent -> child relation,
|
||||
// starting from the parent.
|
||||
type Serie struct {
|
||||
ChangeSets []*gerrit.Changeset
|
||||
ChangeSets []*Changeset
|
||||
}
|
||||
|
||||
// GetParentCommitIDs returns the parent commit IDs
|
||||
|
@ -33,9 +31,7 @@ func (s *Serie) GetLeafCommitID() (string, error) {
|
|||
|
||||
// CheckIntegrity checks that the series contains a properly ordered and connected chain of commits
|
||||
func (s *Serie) CheckIntegrity() error {
|
||||
logger := log.WithFields(log.Fields{
|
||||
"serie": s,
|
||||
})
|
||||
logger := log.WithField("serie", s)
|
||||
// an empty serie is invalid
|
||||
if len(s.ChangeSets) == 0 {
|
||||
return fmt.Errorf("An empty serie is invalid")
|
||||
|
@ -71,7 +67,7 @@ func (s *Serie) CheckIntegrity() error {
|
|||
|
||||
// FilterAllChangesets applies a filter function on all of the changesets in the series.
|
||||
// returns true if it returns true for all changesets, false otherwise
|
||||
func (s *Serie) FilterAllChangesets(f func(c *gerrit.Changeset) bool) bool {
|
||||
func (s *Serie) FilterAllChangesets(f func(c *Changeset) bool) bool {
|
||||
for _, changeset := range s.ChangeSets {
|
||||
if f(changeset) == false {
|
||||
return false
|
|
@ -1,34 +1,33 @@
|
|||
package submitqueue
|
||||
package gerrit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/apex/log"
|
||||
)
|
||||
|
||||
// AssembleSeries consumes a list of `Changeset`, and groups them together to series
|
||||
//
|
||||
// We initially put every Changeset in its own Serie
|
||||
//
|
||||
// As we have no control over the order of the passed changesets,
|
||||
// we maintain two lookup tables,
|
||||
// mapLeafToSerie, which allows to lookup a serie by its leaf commit id,
|
||||
// to append to an existing serie
|
||||
// and mapParentToSeries, which allows to lookup all series having a certain parent commit id,
|
||||
// to prepend to any of the existing series
|
||||
// if we can't find anything, we create a new series
|
||||
func AssembleSeries(changesets []*gerrit.Changeset, log *logrus.Logger) ([]*Serie, error) {
|
||||
// we maintain a lookup table, mapLeafToSerie,
|
||||
// which allows to lookup a serie by its leaf commit id
|
||||
// We concat series in a fixpoint approach
|
||||
// because both appending and prepending is much more complex.
|
||||
// Concatenation moves changesets of the later changeset in the previous one
|
||||
// in a cleanup phase, we remove orphaned series (those without any changesets inside)
|
||||
// afterwards, we do an integrity check, just to be on the safe side.
|
||||
func AssembleSeries(changesets []*Changeset, log *log.Logger) ([]*Serie, error) {
|
||||
series := make([]*Serie, 0)
|
||||
mapLeafToSerie := make(map[string]*Serie, 0)
|
||||
|
||||
for _, changeset := range changesets {
|
||||
logger := log.WithFields(logrus.Fields{
|
||||
"changeset": changeset.String(),
|
||||
})
|
||||
logger := log.WithField("changeset", changeset.String())
|
||||
|
||||
logger.Debug("creating initial serie")
|
||||
serie := &Serie{
|
||||
ChangeSets: []*gerrit.Changeset{changeset},
|
||||
ChangeSets: []*Changeset{changeset},
|
||||
}
|
||||
series = append(series, serie)
|
||||
mapLeafToSerie[changeset.CommitID] = serie
|
||||
|
@ -72,7 +71,7 @@ func AssembleSeries(changesets []*gerrit.Changeset, log *logrus.Logger) ([]*Seri
|
|||
mapLeafToSerie[myLeafCommitID] = otherSerie
|
||||
|
||||
// orphan our serie
|
||||
serie.ChangeSets = []*gerrit.Changeset{}
|
||||
serie.ChangeSets = []*Changeset{}
|
||||
// remove the orphaned serie from the lookup table
|
||||
delete(mapLeafToSerie, myLeafCommitID)
|
||||
|
||||
|
@ -90,9 +89,7 @@ func AssembleSeries(changesets []*gerrit.Changeset, log *logrus.Logger) ([]*Seri
|
|||
|
||||
// Check integrity, just to be on the safe side.
|
||||
for _, serie := range series {
|
||||
logger := log.WithFields(logrus.Fields{
|
||||
"serie": serie.String(),
|
||||
})
|
||||
logger := log.WithField("serie", serie.String())
|
||||
logger.Debugf("checking integrity")
|
||||
err := serie.CheckIntegrity()
|
||||
if err != nil {
|
2
go.mod
2
go.mod
|
@ -4,9 +4,9 @@ go 1.12
|
|||
|
||||
require (
|
||||
github.com/andygrunwald/go-gerrit v0.0.0-20190825170856-5959a9bf9ff8
|
||||
github.com/apex/log v1.1.1
|
||||
github.com/gin-gonic/gin v1.4.0
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/rakyll/statik v0.1.6
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/urfave/cli v1.22.1
|
||||
)
|
||||
|
|
58
go.sum
58
go.sum
|
@ -1,55 +1,87 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/andygrunwald/go-gerrit v0.0.0-20190825170856-5959a9bf9ff8 h1:9PvNa6zH6gOW4VVfbAx5rjDLpxunG+RSaXQB+8TEv4w=
|
||||
github.com/andygrunwald/go-gerrit v0.0.0-20190825170856-5959a9bf9ff8/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk=
|
||||
github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
|
||||
github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
|
||||
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
|
||||
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
|
||||
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
49
main.go
49
main.go
|
@ -14,16 +14,13 @@ import (
|
|||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/apex/log"
|
||||
"github.com/apex/log/handlers/memory"
|
||||
"github.com/apex/log/handlers/multi"
|
||||
"github.com/apex/log/handlers/text"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// configure logging
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
//log.SetFormatter(&log.JSONFormatter{})
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
var URL, username, password, projectName, branchName, submitQueueTag string
|
||||
var fetchOnly bool
|
||||
|
||||
|
@ -81,25 +78,33 @@ func main() {
|
|||
},
|
||||
}
|
||||
|
||||
memoryLogHandler := memory.New()
|
||||
l := &log.Logger{
|
||||
Handler: multi.New(
|
||||
text.New(os.Stderr),
|
||||
memoryLogHandler,
|
||||
),
|
||||
Level: log.DebugLevel,
|
||||
}
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
gerritClient, err := gerrit.NewClient(URL, username, password)
|
||||
gerrit, err := gerrit.NewClient(l, URL, username, password, projectName, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Successfully connected to gerrit at %s", URL)
|
||||
log.Infof("Successfully connected to gerrit at %s", URL)
|
||||
|
||||
submitQueue := submitqueue.MakeSubmitQueue(gerritClient, projectName, branchName, submitQueueTag)
|
||||
runner := submitqueue.NewRunner(submitQueue)
|
||||
runner := submitqueue.NewRunner(l, gerrit, submitQueueTag)
|
||||
|
||||
handler := frontend.MakeFrontend(runner)
|
||||
handler := frontend.MakeFrontend(memoryLogHandler, gerrit, runner)
|
||||
|
||||
// fetch only on first run
|
||||
runner.Trigger(true)
|
||||
runner.Trigger(fetchOnly)
|
||||
|
||||
// ticker
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Minute * 10)
|
||||
time.Sleep(time.Minute * 5)
|
||||
runner.Trigger(fetchOnly)
|
||||
}
|
||||
}()
|
||||
|
@ -111,7 +116,7 @@ func main() {
|
|||
|
||||
server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -119,21 +124,9 @@ func main() {
|
|||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// mux := http.NewServeMux()
|
||||
|
||||
// options := &gerrit.EventsLogOptions{}
|
||||
// events, _, _, err := gerritClient.EventsLog.GetEvents(options)
|
||||
|
||||
// TODOS:
|
||||
// - create submit queue user
|
||||
// - handle event log, either by accepting webhooks, or by streaming events?
|
||||
|
||||
//n := negroni.Classic()
|
||||
//n.UseHandler(mux)
|
||||
|
||||
//fmt.Println("Listening on :3000…")
|
||||
//http.ListenAndServe(":3000", n)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{ define "serie" }}
|
||||
<tr>
|
||||
<td colspan="3" class="{{ if not (. | isAutoSubmittable) }}table-primary{{ else }}table-success{{ end }}">Serie with {{ len .ChangeSets }} changes</td>
|
||||
<td colspan="3" class="table-success">Serie with {{ len .ChangeSets }} changes</td>
|
||||
</tr>
|
||||
{{ range $changeset := .ChangeSets }}
|
||||
{{ block "changeset" $changeset }}{{ end }}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
{{ define "series" }}
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th scope="col">Owner</th>
|
||||
<th scope="col">Changeset</th>
|
||||
<th scope="col">Flags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $serie := . }}
|
||||
{{ block "serie" $serie }}{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
|
@ -42,63 +42,37 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Currently running:</th>
|
||||
<td>
|
||||
{{ if .currentlyRunning }}
|
||||
started at {{ .currentlyRunning.Format "2006-01-02 15:04:05 UTC" }}
|
||||
{{ else }}
|
||||
<span class="text-secondary">Not currently running</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
{{ if .currentlyRunning }}yes{{ else }}no{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">HEAD:</th>
|
||||
<td>
|
||||
{{ if .currentlyRunning }}{{ .HEAD }}{{ else }}-{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="region-log">Log</h2>
|
||||
|
||||
<div id="history-accordion">
|
||||
{{ range $i, $result := .results }}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>
|
||||
<button class="btn btn-link" data-toggle="collapse" data-target="#history-collapse-{{ $i }}">
|
||||
Result Item {{ $i }}, {{ $result.StartTime.Format "2006-01-02 15:04:05 UTC"}} - {{ $result.EndTime.Format "2006-01-02 15:04:05 UTC"}}
|
||||
</button>
|
||||
</h5>
|
||||
</div>
|
||||
<div id="history-collapse-{{ $i }}" class="collapse {{ if eq $i 0 }} show{{ end }}" data-parent="#history-accordion">
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">HEAD:</th>
|
||||
<td><code>{{ .HEAD }}</code></td>
|
||||
</tr>
|
||||
{{ if $result.Error }}
|
||||
<tr>
|
||||
<th scope="row">Error:</th>
|
||||
<td class="text-danger">{{ $result.Error }}</td>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
<tr>
|
||||
<th scope="row">Log:</th>
|
||||
<td class="bg-dark text-white">
|
||||
{{ range $logEntry := $result.LogEntries }}
|
||||
<code>{{ $logEntry }}</code><br />
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{{ block "series" $result.Series}}{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div> <!-- .container -->
|
||||
<h2 id="region-wipserie">wip Serie</h2>
|
||||
{{ if .wipSerie }}
|
||||
{{ block "serie" .wipSerie }}{{ end }}
|
||||
{{ else }}
|
||||
-
|
||||
{{ end }}
|
||||
|
||||
<h2 id="region-log">Log</h2>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="bg-dark text-white">
|
||||
{{ range $entry := .memory.Entries }}
|
||||
<code>{{ $entry }}</code><br />
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package submitqueue
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Problem: no inspection during the run
|
||||
// Problem: record the state
|
||||
|
||||
// Result contains all data necessary to inspect a previous run
|
||||
// This includes the Series from that run, and all Log Entries collected.
|
||||
// It also implements the interface required for logrus.Hook.
|
||||
type Result struct {
|
||||
LogEntries []*logrus.Entry
|
||||
Series []Serie
|
||||
Error error
|
||||
startTime time.Time
|
||||
HEAD string
|
||||
}
|
||||
|
||||
// MakeResult produces a new Result struct,
|
||||
// and initializes startTime with the current time.
|
||||
func MakeResult() *Result {
|
||||
return &Result{
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// StartTime returns the startTime
|
||||
func (r Result) StartTime() time.Time {
|
||||
return r.startTime
|
||||
}
|
||||
|
||||
// EndTime returns the time of the latest log entry
|
||||
func (r Result) EndTime() time.Time {
|
||||
if len(r.LogEntries) == 0 {
|
||||
return r.startTime
|
||||
}
|
||||
return r.LogEntries[len(r.LogEntries)-1].Time
|
||||
}
|
||||
|
||||
// Fire is called by logrus on each log event,
|
||||
// we collect all log entries in the struct variable
|
||||
func (r *Result) Fire(entry *logrus.Entry) error {
|
||||
r.LogEntries = append(r.LogEntries, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels is called by logrus to determine whether to Fire the handler.
|
||||
// As we want to collect all log entries, we return logrus.AllLevels
|
||||
func (r *Result) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
|
@ -1,60 +1,203 @@
|
|||
package submitqueue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
|
||||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
)
|
||||
|
||||
// Runner supervises the submit queue and records historical data about it
|
||||
// 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
|
||||
submitQueue *SubmitQueue
|
||||
currentlyRunning *time.Time
|
||||
results []*Result
|
||||
currentlyRunning bool
|
||||
wipSerie *gerrit.Serie
|
||||
logger *log.Logger
|
||||
gerrit *gerrit.Client
|
||||
submitQueueTag string // the tag used to submit something to the submit queue
|
||||
}
|
||||
|
||||
// NewRunner initializes a new runner object
|
||||
func NewRunner(sq *SubmitQueue) *Runner {
|
||||
// NewRunner creates a new Runner struct
|
||||
func NewRunner(logger *log.Logger, gerrit *gerrit.Client, submitQueueTag string) *Runner {
|
||||
return &Runner{
|
||||
submitQueue: sq,
|
||||
results: []*Result{},
|
||||
logger: logger,
|
||||
gerrit: gerrit,
|
||||
submitQueueTag: submitQueueTag,
|
||||
}
|
||||
}
|
||||
|
||||
// GetState returns a copy of all the state for the frontend
|
||||
func (r *Runner) GetState() (SubmitQueue, *time.Time, []*Result) {
|
||||
r.mut.Lock()
|
||||
defer r.mut.Unlock()
|
||||
return *r.submitQueue, r.currentlyRunning, r.results
|
||||
// isAutoSubmittable determines if something could be autosubmitted, potentially requiring a rebase
|
||||
// for this, it needs to:
|
||||
// * have the auto-submit label
|
||||
// * has +2 review
|
||||
// * has +1 CI
|
||||
func (r *Runner) isAutoSubmittable(s *gerrit.Serie) bool {
|
||||
for _, c := range s.ChangeSets {
|
||||
if c.Verified != 1 || c.CodeReviewed != 2 || !c.HasTag(r.submitQueueTag) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Trigger starts a new batch job
|
||||
// TODO: make sure only one batch job is started at the same time
|
||||
// if a batch job is already started, ignore the newest request
|
||||
// TODO: be more granular in dry-run mode
|
||||
func (r *Runner) Trigger(fetchOnly bool) {
|
||||
// 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()
|
||||
if r.currentlyRunning != nil {
|
||||
return
|
||||
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")
|
||||
}
|
||||
now := time.Now()
|
||||
r.currentlyRunning = &now
|
||||
r.currentlyRunning = true
|
||||
r.mut.Unlock()
|
||||
|
||||
defer func() {
|
||||
r.mut.Lock()
|
||||
r.currentlyRunning = nil
|
||||
r.currentlyRunning = false
|
||||
r.mut.Unlock()
|
||||
}()
|
||||
|
||||
result := r.submitQueue.Run(fetchOnly)
|
||||
|
||||
r.mut.Lock()
|
||||
// drop tail if size > 10
|
||||
if len(r.results) > 10 {
|
||||
r.results = append([]*Result{result}, r.results[:9]...)
|
||||
} else {
|
||||
r.results = append([]*Result{result}, r.results...)
|
||||
// isReady means a series is auto submittbale and rebased on HEAD
|
||||
isReady := func(s *gerrit.Serie) bool {
|
||||
return r.isAutoSubmittable(s) && r.gerrit.SerieIsRebasedOnHEAD(s)
|
||||
}
|
||||
r.mut.Unlock()
|
||||
|
||||
isAwaitingCI := func(s *gerrit.Serie) bool {
|
||||
for _, c := range s.ChangeSets {
|
||||
if !(c.Verified == 0 && c.CodeReviewed != 2 && c.HasTag(r.submitQueueTag)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Prepare the work by creating a local cache of gerrit state
|
||||
r.gerrit.Refresh()
|
||||
|
||||
// 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")
|
||||
|
||||
if !r.gerrit.SerieIsRebasedOnHEAD(r.wipSerie) {
|
||||
// check for chaos monkeys
|
||||
l.Warnf("HEAD has moved to {} while still waiting for wipSerie, discarding it", r.gerrit.GetHEAD())
|
||||
r.wipSerie = nil
|
||||
} else if isAwaitingCI(r.wipSerie) {
|
||||
// the changeset is still awaiting for CI feedback
|
||||
l.Info("keep waiting for wipSerie")
|
||||
|
||||
// break the loop, take a look at it at the next trigger.
|
||||
break
|
||||
} else if isReady(r.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?!
|
||||
}
|
||||
}
|
||||
|
||||
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(isReady)
|
||||
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("nothing to do, 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
|
||||
}
|
||||
// it doesn't matter this serie isn't in its rebased state,
|
||||
// we'll refetch it on the beginning of the next trigger anyways
|
||||
r.wipSerie = serie
|
||||
break
|
||||
}
|
||||
|
||||
r.logger.Info("Run complete")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
package submitqueue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tweag/gerrit-queue/gerrit"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SubmitQueue contains a list of series, a gerrit connection, and some project configuration
|
||||
type SubmitQueue struct {
|
||||
Series []*Serie
|
||||
gerrit gerrit.IClient
|
||||
ProjectName string
|
||||
BranchName string
|
||||
HEAD string
|
||||
SubmitQueueTag string // the tag used to submit something to the submit queue
|
||||
URL string
|
||||
}
|
||||
|
||||
// MakeSubmitQueue builds a new submit queue
|
||||
func MakeSubmitQueue(gerritClient gerrit.IClient, projectName string, branchName string, submitQueueTag string) *SubmitQueue {
|
||||
return &SubmitQueue{
|
||||
Series: make([]*Serie, 0),
|
||||
gerrit: gerritClient,
|
||||
ProjectName: projectName,
|
||||
BranchName: branchName,
|
||||
SubmitQueueTag: submitQueueTag,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSeries fills .Series by searching changesets, and assembling them to Series.
|
||||
func (s *SubmitQueue) LoadSeries(log *logrus.Logger) error {
|
||||
var queryString = fmt.Sprintf("status:open project:%s branch:%s", s.ProjectName, s.BranchName)
|
||||
log.Debugf("Running query %s", queryString)
|
||||
|
||||
// Download changesets from gerrit
|
||||
changesets, err := s.gerrit.SearchChangesets(queryString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assemble to series
|
||||
series, err := AssembleSeries(changesets, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort by size
|
||||
s.Series = SortSeries(series)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: clear submit queue tag if missing +1/+2?
|
||||
|
||||
// IsAutoSubmittable returns true if a given Serie has all the necessary flags set
|
||||
// meaning it would be fine to rebase and/or submit it.
|
||||
// This means, every changeset needs to:
|
||||
// - have the s.SubmitQueueTag hashtag
|
||||
// - be verified (+1 by CI)
|
||||
// - be code reviewed (+2 by a human)
|
||||
func (s *SubmitQueue) IsAutoSubmittable(serie *Serie) bool {
|
||||
return serie.FilterAllChangesets(func(c *gerrit.Changeset) bool {
|
||||
return c.HasTag(s.SubmitQueueTag) && c.IsVerified && c.IsCodeReviewed
|
||||
})
|
||||
}
|
||||
|
||||
// GetChangesetURL returns the URL to view a given changeset
|
||||
func (s *SubmitQueue) GetChangesetURL(changeset *gerrit.Changeset) string {
|
||||
return fmt.Sprintf("%s/c/%s/+/%d", s.gerrit.GetBaseURL(), s.ProjectName, changeset.Number)
|
||||
}
|
||||
|
||||
// DoSubmit submits changes that can be submitted,
|
||||
// and updates `Series` to contain the remaining ones
|
||||
// Also updates `HEAD`.
|
||||
func (s *SubmitQueue) DoSubmit(log *logrus.Logger) error {
|
||||
var remainingSeries []*Serie
|
||||
|
||||
// TODO: actually log more!
|
||||
|
||||
for _, serie := range s.Series {
|
||||
serieParentCommitIDs, err := serie.GetParentCommitIDs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we can only submit series with a single parent commit (otherwise they're not rebased)
|
||||
if len(serieParentCommitIDs) != 1 {
|
||||
return fmt.Errorf("%s has more than one parent commit, skipping", serie.String())
|
||||
}
|
||||
|
||||
// if serie is auto-submittable and rebased on top of current master…
|
||||
if s.IsAutoSubmittable(serie) && serieParentCommitIDs[0] == s.HEAD {
|
||||
// submit the last changeset of the series, which submits intermediate ones too
|
||||
_, err := s.gerrit.SubmitChangeset(serie.ChangeSets[len(serie.ChangeSets)-1])
|
||||
if err != nil {
|
||||
// this might fail, for various reasons:
|
||||
// - developers could have updated the changeset meanwhile, clearing +1/+2 bits
|
||||
// - master might have advanced, so this changeset isn't rebased on top of master
|
||||
// TODO: we currently bail out entirely, but should be fine on the
|
||||
// next loop. We might later want to improve the logic to be a bit more
|
||||
// smarter (like log and try with the next one)
|
||||
return err
|
||||
}
|
||||
// advance head to the leaf of the current serie for the next iteration
|
||||
newHead, err := serie.GetLeafCommitID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.HEAD = newHead
|
||||
} else {
|
||||
remainingSeries = append(remainingSeries, serie)
|
||||
}
|
||||
}
|
||||
|
||||
s.Series = remainingSeries
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoRebase rebases the next auto-submittable series on top of current HEAD
|
||||
// they are still ordered by series size
|
||||
// After a DoRebase, consumers are supposed to fetch state again via LoadSeries,
|
||||
// as things most likely have changed, and error handling during partially failed rebases
|
||||
// is really tricky
|
||||
func (s *SubmitQueue) DoRebase(log *logrus.Logger) error {
|
||||
if s.HEAD == "" {
|
||||
return fmt.Errorf("current HEAD is an empty string, bailing out")
|
||||
}
|
||||
for _, serie := range s.Series {
|
||||
logger := log.WithFields(logrus.Fields{
|
||||
"serie": serie,
|
||||
})
|
||||
if !s.IsAutoSubmittable(serie) {
|
||||
logger.Debug("skipping non-auto-submittable series")
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Infof("rebasing on top of %s", s.HEAD)
|
||||
_, err := s.RebaseSerie(serie, s.HEAD)
|
||||
if err != nil {
|
||||
// We skip trivial rebase errors instead of bailing out.
|
||||
// TODO: we might want to remove s.SubmitQueueTag from the changeset,
|
||||
// but even without doing it,
|
||||
// we're merly spanning, and won't get stuck in trying to rebase the same
|
||||
// changeset over and over again, as some other changeset will likely succeed
|
||||
// with rebasing and will be merged by DoSubmit.
|
||||
logger.Warnf("failure while rebasing, continuing with next one: %s", err)
|
||||
continue
|
||||
} else {
|
||||
logger.Info("success rebasing on top of %s", s.HEAD)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run starts the submit and rebase logic.
|
||||
func (s *SubmitQueue) Run(fetchOnly bool) *Result {
|
||||
r := MakeResult()
|
||||
//TODO: log decisions made and add to some ring buffer
|
||||
var err error
|
||||
|
||||
log := logrus.New()
|
||||
log.AddHook(r)
|
||||
|
||||
commitID, err := s.gerrit.GetHEAD(s.ProjectName, s.BranchName)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to retrieve HEAD of branch %s at project %s: %s", s.BranchName, s.ProjectName, err)
|
||||
r.Error = err
|
||||
return r
|
||||
}
|
||||
s.HEAD = commitID
|
||||
r.HEAD = commitID
|
||||
|
||||
err = s.LoadSeries(log)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return r
|
||||
}
|
||||
|
||||
// copy series to result object
|
||||
for _, serie := range s.Series {
|
||||
r.Series = append(r.Series, *serie)
|
||||
}
|
||||
|
||||
if len(s.Series) == 0 {
|
||||
// Nothing to do!
|
||||
log.Warn("Nothing to do here")
|
||||
return r
|
||||
}
|
||||
if fetchOnly {
|
||||
return r
|
||||
}
|
||||
err = s.DoSubmit(log)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return r
|
||||
}
|
||||
err = s.DoRebase(log)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return r
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RebaseSerie rebases a whole serie on top of a given ref
|
||||
// TODO: only rebase a single changeset. we don't really want to join disconnected series, by rebasing them on top of each other.
|
||||
func (s *SubmitQueue) RebaseSerie(serie *Serie, ref string) (*Serie, error) {
|
||||
newSeries := &Serie{
|
||||
ChangeSets: make([]*gerrit.Changeset, len(serie.ChangeSets)),
|
||||
}
|
||||
|
||||
rebaseOnto := ref
|
||||
for _, changeset := range serie.ChangeSets {
|
||||
newChangeset, err := s.gerrit.RebaseChangeset(changeset, rebaseOnto)
|
||||
|
||||
if err != nil {
|
||||
// uh-oh…
|
||||
// TODO: think about error handling
|
||||
// TODO: remove the submit queue tag if the rebase fails (but only then, not on other errors)
|
||||
return newSeries, err
|
||||
}
|
||||
newSeries.ChangeSets = append(newSeries.ChangeSets, newChangeset)
|
||||
|
||||
// the next changeset should be rebased on top of the current commit
|
||||
rebaseOnto = newChangeset.CommitID
|
||||
}
|
||||
return newSeries, nil
|
||||
}
|
Loading…
Reference in a new issue