diff options
Diffstat (limited to 'gerrit/series.go')
-rw-r--r-- | gerrit/series.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/gerrit/series.go b/gerrit/series.go new file mode 100644 index 000000000000..06d49805d35d --- /dev/null +++ b/gerrit/series.go @@ -0,0 +1,122 @@ +package gerrit + +import ( + "sort" + + "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 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.WithField("changeset", changeset.String()) + + logger.Debug("creating initial serie") + serie := &Serie{ + ChangeSets: []*Changeset{changeset}, + } + series = append(series, serie) + mapLeafToSerie[changeset.CommitID] = serie + } + + // Combine series using a fixpoint approach, with a max iteration count. + log.Debug("glueing together phase") + for i := 1; i < 100; i++ { + didUpdate := false + log.Debugf("at iteration %d", i) + for _, serie := range series { + logger := log.WithField("serie", serie.String()) + parentCommitIDs, err := serie.GetParentCommitIDs() + if err != nil { + return series, err + } + if len(parentCommitIDs) != 1 { + // We can't append merge commits to other series + logger.Infof("No single parent, skipping.") + continue + } + parentCommitID := parentCommitIDs[0] + logger.Debug("Looking for a predecessor.") + // if there's another serie that has this parent as a leaf, glue together + if otherSerie, ok := mapLeafToSerie[parentCommitID]; ok { + if otherSerie == serie { + continue + } + logger := logger.WithField("otherSerie", otherSerie) + + myLeafCommitID, err := serie.GetLeafCommitID() + if err != nil { + return series, err + } + + // append our changesets to the other serie + logger.Debug("Splicing together.") + otherSerie.ChangeSets = append(otherSerie.ChangeSets, serie.ChangeSets...) + + delete(mapLeafToSerie, parentCommitID) + mapLeafToSerie[myLeafCommitID] = otherSerie + + // orphan our serie + serie.ChangeSets = []*Changeset{} + // remove the orphaned serie from the lookup table + delete(mapLeafToSerie, myLeafCommitID) + + didUpdate = true + } else { + logger.Debug("Not found.") + } + } + series = removeOrphanedSeries(series) + if !didUpdate { + log.Infof("converged after %d iterations", i) + break + } + } + + // Check integrity, just to be on the safe side. + for _, serie := range series { + logger := log.WithField("serie", serie.String()) + logger.Debugf("checking integrity") + err := serie.CheckIntegrity() + if err != nil { + logger.Errorf("checking integrity failed: %s", err) + } + } + return series, nil +} + +// removeOrphanedSeries removes all empty series (that contain zero changesets) +func removeOrphanedSeries(series []*Serie) []*Serie { + newSeries := []*Serie{} + for _, serie := range series { + if len(serie.ChangeSets) != 0 { + newSeries = append(newSeries, serie) + } + } + return newSeries +} + +// SortSeries sorts a list of series by the number of changesets in each serie, descending +func SortSeries(series []*Serie) []*Serie { + newSeries := make([]*Serie, len(series)) + copy(newSeries, series) + sort.Slice(newSeries, func(i, j int) bool { + // the weight depends on the amount of changesets series changeset size + return len(series[i].ChangeSets) > len(series[j].ChangeSets) + }) + return newSeries +} |