[repo graph] Implement some git methods, restrict underlying repo
Additionally, don't lock the graph during "git fetch"; only lock the
underlying repo.
Bug: skia:
Change-Id: I0e8afbb1f932719668762f9cbb4c4488d26fcd83
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/198862
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Ben Wagner <benjaminwagner@google.com>
diff --git a/datahopper/go/bot_metrics/bot_metrics.go b/datahopper/go/bot_metrics/bot_metrics.go
index 58a5b0c..444ea68 100644
--- a/datahopper/go/bot_metrics/bot_metrics.go
+++ b/datahopper/go/bot_metrics/bot_metrics.go
@@ -263,10 +263,10 @@
}
// For each commit covered by the task, record the lag time.
- if err := c.Recurse(func(commit *repograph.Commit) (bool, error) {
+ if err := c.Recurse(func(commit *repograph.Commit) error {
// Prevent us from tracing through the entire commit history.
if commit.Timestamp.Before(now.Add(-COMMIT_TASK_WINDOW)) {
- return false, nil
+ return repograph.ErrStopRecursing
}
// Get the cached data for this commit.
@@ -292,7 +292,7 @@
})
if err == task_cfg_cache.ErrNoSuchEntry {
sklog.Warningf("TaskCfgCache has no entry for %s@%s.", repoUrl, commit.Hash)
- return true, nil
+ return nil
} else if err != nil {
// Some old commits only have tasks without jobs. Skip them.
if strings.Contains(err.Error(), "is not reachable by any Job") {
@@ -300,9 +300,9 @@
Tasks: map[string]*specs.TaskSpec{},
Jobs: map[string]*specs.JobSpec{},
}
- return false, nil
+ return repograph.ErrStopRecursing
}
- return false, err
+ return err
}
cfg = c
cfgs[commit] = cfg
@@ -320,7 +320,7 @@
}
if _, ok := cfg.Tasks[t.Name]; !ok {
- return false, nil
+ return repograph.ErrStopRecursing
}
}
@@ -331,12 +331,12 @@
// one, record the lag time and keep recursing.
if best, ok := cData.Tasks[t.Name]; !ok || best > d {
cData.Tasks[t.Name] = d
- return true, nil
+ return nil
}
// This commit was covered by a previous task,
// so all previous commits will also be covered.
- return false, nil
+ return repograph.ErrStopRecursing
}); err != nil {
return err
}
diff --git a/datahopper/go/datahopper/jobs.go b/datahopper/go/datahopper/jobs.go
index f63bba9..835cb2b 100644
--- a/datahopper/go/datahopper/jobs.go
+++ b/datahopper/go/datahopper/jobs.go
@@ -380,19 +380,19 @@
}
var commit *repograph.Commit
var cfg *specs.TasksCfg
- if err := head.Recurse(func(c *repograph.Commit) (bool, error) {
+ if err := head.Recurse(func(c *repograph.Commit) error {
tasksCfg, err := tcc.Get(ctx, types.RepoState{
Repo: repoUrl,
Revision: c.Hash,
})
if err == task_cfg_cache.ErrNoSuchEntry {
- return true, nil
+ return nil
} else if err != nil {
- return false, err
+ return err
}
cfg = tasksCfg
commit = c
- return false, nil
+ return repograph.ErrStopRecursing
}); err != nil {
return nil, nil, err
}
@@ -432,12 +432,12 @@
// Iterate backwards to find the most-recently tested commit. We're not going to worry about
// merges -- if a job was run on both branches, we'll use the first commit we come across.
- if err := head.Recurse(func(c *repograph.Commit) (bool, error) {
+ if err := head.Recurse(func(c *repograph.Commit) error {
// Stop if this commit is outside the scheduling window.
if in, err := m.window.TestCommitHash(repoUrl, c.Hash); err != nil {
- return false, sklog.FmtErrorf("TestCommitHash: %s", err)
+ return sklog.FmtErrorf("TestCommitHash: %s", err)
} else if !in {
- return false, nil
+ return repograph.ErrStopRecursing
}
rs := types.RepoState{
Repo: repoUrl,
@@ -447,7 +447,7 @@
for name := range todo {
jobs, err := m.jCache.GetJobsByRepoState(name, rs)
if err != nil {
- return false, sklog.FmtErrorf("GetJobsByRepoState: %s", err)
+ return sklog.FmtErrorf("GetJobsByRepoState: %s", err)
}
for _, j := range jobs {
if j.Done() {
@@ -457,12 +457,12 @@
}
}
if len(todo) == 0 {
- return false, nil
+ return repograph.ErrStopRecursing
}
// Check that the remaining JobSpecs are still valid at this commit.
taskCfg, err := m.taskCfgCache.Get(ctx, rs)
if err != nil {
- return false, sklog.FmtErrorf("Error reading TaskCfg for %q at %q: %s", repoUrl, c.Hash, err)
+ return sklog.FmtErrorf("Error reading TaskCfg for %q at %q: %s", repoUrl, c.Hash, err)
}
for name := range todo {
if _, ok := taskCfg.Jobs[name]; !ok {
@@ -472,7 +472,10 @@
times[name] = c.Timestamp
}
}
- return len(todo) > 0, nil
+ if len(todo) > 0 {
+ return nil
+ }
+ return repograph.ErrStopRecursing
}); err != nil {
return err
}
diff --git a/datahopper/go/datahopper/jobs_test.go b/datahopper/go/datahopper/jobs_test.go
index 25f54d2..a53d12a 100644
--- a/datahopper/go/datahopper/jobs_test.go
+++ b/datahopper/go/datahopper/jobs_test.go
@@ -465,7 +465,7 @@
check("0", c1age, c2age)
// Revert back to c1 (no Perf task) and check that Perf job disappears.
- content, err := repo.Repo().GetFile(ctx, "infra/bots/tasks.json", c1)
+ content, err := repo.GetFile(ctx, "infra/bots/tasks.json", c1)
assert.NoError(t, err)
gb.Add(ctx, "infra/bots/tasks.json", content)
c3 := gb.CommitMsgAt(ctx, "c3", c1time.Add(10*time.Second)) // 5 seconds after c2
diff --git a/datahopper/go/datahopper/main.go b/datahopper/go/datahopper/main.go
index ee0d04d..95d20ce 100644
--- a/datahopper/go/datahopper/main.go
+++ b/datahopper/go/datahopper/main.go
@@ -174,18 +174,10 @@
skiaGauge := metrics2.GetInt64Metric("repo_commits", map[string]string{"repo": "skia"})
infraGauge := metrics2.GetInt64Metric("repo_commits", map[string]string{"repo": "infra"})
for range time.Tick(5 * time.Minute) {
- nSkia, err := repos[common.REPO_SKIA].Repo().NumCommits(ctx)
- if err != nil {
- sklog.Errorf("Failed to get number of commits for Skia: %s", err)
- } else {
- skiaGauge.Update(nSkia)
- }
- nInfra, err := repos[common.REPO_SKIA_INFRA].Repo().NumCommits(ctx)
- if err != nil {
- sklog.Errorf("Failed to get number of commits for Infra: %s", err)
- } else {
- infraGauge.Update(nInfra)
- }
+ nSkia := repos[common.REPO_SKIA].Len()
+ skiaGauge.Update(int64(nSkia))
+ nInfra := repos[common.REPO_SKIA_INFRA].Len()
+ infraGauge.Update(int64(nInfra))
}
}()
diff --git a/go/git/repograph/graph.go b/go/git/repograph/graph.go
index 5ab447b..4943bd0 100644
--- a/go/git/repograph/graph.go
+++ b/go/git/repograph/graph.go
@@ -7,6 +7,7 @@
import (
"context"
"encoding/gob"
+ "errors"
"fmt"
"io"
"path"
@@ -27,55 +28,56 @@
CACHE_FILE = "sk_gitrepo.gob"
)
+var (
+ ErrStopRecursing = errors.New("Stop recursing")
+)
+
// Commit represents a commit in a Git repo.
type Commit struct {
*vcsinfo.LongCommit
ParentIndices []int
+ parents []*Commit
repo *Graph
}
// Parents returns the parents of this commit.
func (c *Commit) GetParents() []*Commit {
- rv := make([]*Commit, 0, len(c.ParentIndices))
- for _, idx := range c.ParentIndices {
- rv = append(rv, c.repo.commitsData[idx])
- }
- return rv
+ return c.parents
}
// Recurse runs the given function recursively over commit history, starting
// at the given commit. The function accepts the current Commit as a parameter.
-// Returning false from the function indicates that recursion should stop for
-// the current branch, however, recursion will continue for any other branches
-// until they are similarly terminated. Returning an error causes recursion to
-// stop without properly terminating other branchces. The error will bubble to
-// the top and be returned. Here's an example of printing out the entire
-// ancestry of a given commit:
+// Returning ErrStopRecursing from the function indicates that recursion should
+// stop for the current branch, however, recursion will continue for any other
+// branches until they are similarly terminated. Returning any other error
+// causes recursion to stop without properly terminating other branches. The
+// error will bubble to the top and be returned. Here's an example of printing
+// out the entire ancestry of a given commit:
//
-// commit.Recurse(func(c *Commit) (bool, error) {
+// commit.Recurse(func(c *Commit) error {
// sklog.Info(c.Hash)
-// return true, nil
+// return nil
// })
-func (c *Commit) Recurse(f func(*Commit) (bool, error)) error {
- return c.recurse(f, make(map[*Commit]bool, len(c.repo.commitsData)))
+func (c *Commit) Recurse(f func(*Commit) error) error {
+ return c.recurse(f, make(map[*Commit]bool, c.repo.Len()))
}
// recurse is a helper function used by Recurse.
-func (c *Commit) recurse(f func(*Commit) (bool, error), visited map[*Commit]bool) error {
+func (c *Commit) recurse(f func(*Commit) error, visited map[*Commit]bool) (rvErr error) {
// For large repos, we may not have enough stack space to recurse
// through the whole commit history. Since most commits only have
// one parent, avoid recursion when possible.
for {
visited[c] = true
- keepGoing, err := f(c)
- if err != nil {
+ if err := f(c); err == ErrStopRecursing {
+ return nil
+ } else if err != nil {
return err
}
- if !keepGoing {
+ if len(c.parents) == 0 {
return nil
- }
- if len(c.ParentIndices) == 1 {
- p := c.repo.commitsData[c.ParentIndices[0]]
+ } else if len(c.parents) == 1 {
+ p := c.parents[0]
if visited[p] {
return nil
}
@@ -84,27 +86,43 @@
break
}
}
- for _, parentIdx := range c.ParentIndices {
- p := c.repo.commitsData[parentIdx]
- if visited[p] {
- continue
- }
- if err := p.recurse(f, visited); err != nil {
- return err
+ if len(c.Parents) > 1 {
+ for _, p := range c.parents {
+ if visited[p] {
+ continue
+ }
+ if err := p.recurse(f, visited); err != nil {
+ return err
+ }
}
}
return nil
}
+// AllCommits returns the hashes of all commits reachable from this Commit, in
+// reverse topological order.
+func (c *Commit) AllCommits() ([]string, error) {
+ commits := make(map[*Commit]bool, c.repo.Len())
+ if err := c.recurse(func(c *Commit) error {
+ return nil
+ }, commits); err != nil {
+ return nil, err
+ }
+
+ // Topologically sort the commits.
+ sorted := topologicalSortHelper(commits)
+ return CommitSlice(sorted).Hashes(), nil
+}
+
// HasAncestor returns true iff the given commit is an ancestor of this commit.
func (c *Commit) HasAncestor(other string) bool {
found := false
- if err := c.Recurse(func(commit *Commit) (bool, error) {
+ if err := c.Recurse(func(commit *Commit) error {
if commit.Hash == other {
found = true
- return false, nil
+ return ErrStopRecursing
}
- return true, nil
+ return nil
}); err != nil {
// Our function doesn't return an error, so we shouldn't hit
// this case.
@@ -113,11 +131,25 @@
return found
}
+// Less compares this Commit to the other Commit and returns true if it is
+// considered "less" than the other. This is used as a helper function in
+// sorting to ensure stability. Uses the timestamp as the primary sort function
+// and the hash as a tie breaker. Note that, because we sort in reverse
+// chronological order, this has the opposite meaning that it seems it should.
+func (c *Commit) Less(other *Commit) bool {
+ if c.Timestamp.Equal(other.Timestamp) {
+ // If the timestamps are equal, just use the commit hash as an
+ // arbitrary way of comparing the two.
+ return c.Hash > other.Hash
+ }
+ return c.Timestamp.After(other.Timestamp)
+}
+
// Helpers for sorting.
type CommitSlice []*Commit
func (s CommitSlice) Len() int { return len(s) }
-func (s CommitSlice) Less(a, b int) bool { return s[a].Timestamp.After(s[b].Timestamp) }
+func (s CommitSlice) Less(a, b int) bool { return s[a].Less(s[b]) }
func (s CommitSlice) Swap(a, b int) { s[a], s[b] = s[b], s[a] }
// Hashes returns a slice of commit hashes corresponding to the commits in s.
@@ -134,8 +166,9 @@
branches []*git.Branch
commits map[string]int
commitsData []*Commit
- mtx sync.RWMutex
+ graphMtx sync.RWMutex
repo *git.Repo
+ repoMtx sync.Mutex
}
// gobGraph is a utility struct used for serializing a Graph using gob.
@@ -166,6 +199,9 @@
}
for _, c := range rv.commitsData {
c.repo = rv
+ for _, parentIdx := range c.ParentIndices {
+ c.parents = append(c.parents, rv.commitsData[parentIdx])
+ }
}
return rv, nil
}
@@ -182,27 +218,24 @@
return New(repo)
}
-// Repo returns the underlying git.Repo object.
-func (r *Graph) Repo() *git.Repo {
- return r.repo
-}
-
// Len returns the number of commits in the repo.
func (r *Graph) Len() int {
- r.mtx.RLock()
- defer r.mtx.RUnlock()
+ r.graphMtx.RLock()
+ defer r.graphMtx.RUnlock()
return len(r.commitsData)
}
+// addCommit adds the commit with the given hash to the Graph. Assumes that the
+// caller holds r.repoMtx and r.graphMtx.
func (r *Graph) addCommit(ctx context.Context, hash string) error {
d, err := r.repo.Details(ctx, hash)
if err != nil {
return fmt.Errorf("repograph.Graph: Failed to obtain Git commit details: %s", err)
}
- var parents []int
+ var parentIndices []int
+ var parents []*Commit
if len(d.Parents) > 0 {
- parentIndices := make([]int, 0, len(d.Parents))
for _, h := range d.Parents {
if h == "" {
continue
@@ -212,15 +245,14 @@
return fmt.Errorf("repograph.Graph: Could not find parent commit %q", h)
}
parentIndices = append(parentIndices, p)
- }
- if len(parentIndices) > 0 {
- parents = parentIndices
+ parents = append(parents, r.commitsData[p])
}
}
c := &Commit{
LongCommit: d,
- ParentIndices: parents,
+ ParentIndices: parentIndices,
+ parents: parents,
repo: r,
}
r.commits[hash] = len(r.commitsData)
@@ -243,8 +275,8 @@
}
func (r *Graph) update(ctx context.Context, returnNewCommits bool) ([]*vcsinfo.LongCommit, error) {
- r.mtx.Lock()
- defer r.mtx.Unlock()
+ r.repoMtx.Lock()
+ defer r.repoMtx.Unlock()
// Update the local copy.
sklog.Infof("Updating repograph.Graph...")
@@ -260,6 +292,8 @@
}
// Load new commits from the repo.
+ r.graphMtx.Lock()
+ defer r.graphMtx.Unlock()
var newCommits []*vcsinfo.LongCommit
if returnNewCommits {
newCommits = []*vcsinfo.LongCommit{}
@@ -342,8 +376,8 @@
// Branches returns the list of known branches in the repo.
func (r *Graph) Branches() []string {
- r.mtx.RLock()
- defer r.mtx.RUnlock()
+ r.graphMtx.RLock()
+ defer r.graphMtx.RUnlock()
rv := make([]string, 0, len(r.branches))
for _, b := range r.branches {
rv = append(rv, b.Name)
@@ -368,8 +402,8 @@
// function does not understand complex ref types (eg. HEAD~3); only branch
// names and full commit hashes are accepted.
func (r *Graph) Get(ref string) *Commit {
- r.mtx.RLock()
- defer r.mtx.RUnlock()
+ r.graphMtx.RLock()
+ defer r.graphMtx.RUnlock()
if c, ok := r.commits[ref]; ok {
return r.commitsData[c]
}
@@ -385,28 +419,27 @@
// RecurseAllBranches runs the given function recursively over the entire commit
// history, starting at each of the known branch heads. The function accepts the
-// current Commit as a parameter. Returning false from the function indicates
-// that recursion should stop for the current branch, however, recursion will
-// continue for any other branches until they are similarly terminated.
-// Returning an error causes recursion to stop without properly terminating
-// other branchces. The error will bubble to the top and be returned. Here's an
-// example of printing out all of the commits in the repo:
+// current Commit as a parameter. Returning ErrStopRecursing from the function
+// indicates that recursion should stop for the current branch, however,
+// recursion will continue for any other branches until they are similarly
+// terminated. Returning any other error causes recursion to stop without
+// properly terminating other branches. The error will bubble to the top and be
+// returned. Here's an example of printing out all of the commits in the repo:
//
// repo.RecurseAllBranches(func(c *Commit) (bool, error) {
// sklog.Info(c.Hash)
// return true, nil
// })
-func (r *Graph) RecurseAllBranches(f func(*Commit) (bool, error)) error {
- r.mtx.RLock()
- defer r.mtx.RUnlock()
- visited := make(map[*Commit]bool, len(r.commitsData))
- for _, b := range r.branches {
- c, ok := r.commits[b.Head]
- if !ok {
- return fmt.Errorf("Branch %s points to unknown commit %s", b.Name, b.Head)
+func (r *Graph) RecurseAllBranches(f func(*Commit) error) error {
+ branches := r.Branches()
+ visited := make(map[*Commit]bool, r.Len())
+ for _, b := range branches {
+ c := r.Get(b)
+ if c == nil {
+ return fmt.Errorf("Branch %s not found", b)
}
- if _, ok := visited[r.commitsData[c]]; !ok {
- if err := r.commitsData[c].recurse(f, visited); err != nil {
+ if _, ok := visited[c]; !ok {
+ if err := c.recurse(f, visited); err != nil {
return err
}
}
@@ -414,15 +447,165 @@
return nil
}
+// RevList is the equivalent of "git rev-list --topo-order from..to".
+// Each argument is a commit hash or a branch name. The commits are returned
+// in reverse topological order. Per "git rev-list" docs, the returned commit
+// hashes consist of all commits reachable from "to" and not reachable from
+// "from". In the typical case of linear git history, this just means the
+// commits on the line after "from" up through "to", but with branches and
+// merges it's possible that there are entire sub-graphs which are reachable
+// from "to" but not "from".
+func (r *Graph) RevList(from, to string) ([]string, error) {
+ fromCommit := r.Get(from)
+ if fromCommit == nil {
+ return nil, fmt.Errorf("Unknown commit %q", from)
+ }
+ toCommit := r.Get(to)
+ if toCommit == nil {
+ return nil, fmt.Errorf("Unknown commit %q", to)
+ }
+ // Shortcut: if the commits are the same, return now.
+ if fromCommit == toCommit {
+ return []string{}, nil
+ }
+
+ // Find all of the excluded commits.
+ exclude := make(map[*Commit]bool, r.Len())
+ if err := fromCommit.recurse(func(c *Commit) error {
+ return nil
+ }, exclude); err != nil {
+ return nil, err
+ }
+
+ // Find the included commits.
+ include := make(map[*Commit]bool, r.Len())
+ if err := toCommit.recurse(func(c *Commit) error {
+ if exclude[c] {
+ return ErrStopRecursing
+ }
+ return nil
+ }, include); err != nil {
+ return nil, err
+ }
+
+ // include may contain some commits from the exclude map; remove them.
+ for c, _ := range include {
+ if exclude[c] {
+ delete(include, c)
+ }
+ }
+
+ // Topologically sort the commits.
+ sorted := topologicalSortHelper(include)
+ return CommitSlice(sorted).Hashes(), nil
+}
+
+// TopologicalSort returns a slice containing the given commits in reverse
+// topological order, ie. every commit is listed before any of its parents.
+func TopologicalSort(commits []*Commit) []*Commit {
+ commitsMap := make(map[*Commit]bool, len(commits))
+ for _, c := range commits {
+ commitsMap[c] = true
+ }
+ return topologicalSortHelper(commitsMap)
+}
+
+// Helper function for TopologicalSort; the caller provides the map, which is
+// modified by topologicalSortHelper.
+func topologicalSortHelper(commits map[*Commit]bool) []*Commit {
+ // children maps each commit to those commits which have it as a parent.
+ children := make(map[*Commit]map[*Commit]bool, len(commits))
+ for c, _ := range commits {
+ for _, p := range c.parents {
+ if commits[p] {
+ subMap, ok := children[p]
+ if !ok {
+ subMap = map[*Commit]bool{}
+ children[p] = subMap
+ }
+ subMap[c] = true
+ }
+ }
+ }
+
+ // Sort the commits topologically.
+ rv := make([]*Commit, 0, len(commits))
+ followBranch := func(c *Commit) {
+ for len(children[c]) == 0 {
+ // Add this commit to rv.
+ rv = append(rv, c)
+ delete(commits, c)
+
+ // Remove this commit from its parents' children, so
+ // that they can be processed.
+ for _, p := range c.parents {
+ if commits[p] {
+ delete(children[p], c)
+ }
+ }
+
+ // Find a parent to process next.
+ var next *Commit
+ for _, p := range c.parents {
+ if commits[p] && len(children[p]) == 0 {
+ // We are ready to process this parent.
+ if next == nil || p.Less(next) {
+ next = p
+ }
+ }
+ }
+ if next != nil {
+ c = next
+ } else {
+ // None of this commit's parents are ready; we can't do
+ // any more.
+ return
+ }
+ }
+ }
+ for len(commits) > 0 {
+ var next *Commit
+ for commit, _ := range commits {
+ if len(children[commit]) == 0 {
+ // We are ready to process this commit.
+ if next == nil || commit.Less(next) {
+ next = commit
+ }
+ }
+ }
+ if next == nil {
+ sklog.Error("No commits are ready to process!")
+ // Return so that we don't loop forever.
+ return rv
+ }
+ followBranch(next)
+ }
+ return rv
+}
+
+// IsAncestor returns true iff A is an ancestor of B, where A and B are either
+// commit hashes or branch names.
+func (r *Graph) IsAncestor(a, b string) (bool, error) {
+ aCommit := r.Get(a)
+ if aCommit == nil {
+ return false, fmt.Errorf("No such commit %q", a)
+ }
+ bCommit := r.Get(b)
+ if bCommit == nil {
+ return false, fmt.Errorf("No such commit %q", b)
+ }
+ return bCommit.HasAncestor(aCommit.Hash), nil
+}
+
// Return any commits at or after the given timestamp.
func (r *Graph) GetCommitsNewerThan(ts time.Time) ([]*vcsinfo.LongCommit, error) {
commits := []*Commit{}
- if err := r.RecurseAllBranches(func(c *Commit) (bool, error) {
+ if err := r.RecurseAllBranches(func(c *Commit) error {
if !c.Timestamp.Before(ts) {
commits = append(commits, c)
- return true, nil
+ return nil
}
- return false, nil
+ return ErrStopRecursing
}); err != nil {
return nil, err
}
@@ -462,6 +645,20 @@
return commits[:n], nil
}
+// TempCheckout returns a git.TempCheckout of the underlying git.Repo.
+func (r *Graph) TempCheckout(ctx context.Context) (*git.TempCheckout, error) {
+ r.repoMtx.Lock()
+ defer r.repoMtx.Unlock()
+ return r.repo.TempCheckout(ctx)
+}
+
+// GetFile returns the contents of the given file at the given commit.
+func (r *Graph) GetFile(ctx context.Context, fileName, commit string) (string, error) {
+ r.repoMtx.Lock()
+ defer r.repoMtx.Unlock()
+ return r.repo.GetFile(ctx, fileName, commit)
+}
+
// Map is a convenience type for dealing with multiple Graphs for different
// repos. The keys are repository URLs.
type Map map[string]*Graph
diff --git a/go/git/repograph/graph_test.go b/go/git/repograph/graph_test.go
index 132ddf1..37dba92 100644
--- a/go/git/repograph/graph_test.go
+++ b/go/git/repograph/graph_test.go
@@ -4,12 +4,17 @@
"context"
"fmt"
"io/ioutil"
+ "os"
"path"
+ "sort"
"testing"
+ "time"
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/go/deepequal"
+ "go.skia.org/infra/go/git"
git_testutils "go.skia.org/infra/go/git/testutils"
+ "go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/util"
)
@@ -97,7 +102,7 @@
assert.Equal(t, []*Commit{c4, c3}, c5.GetParents())
assert.Equal(t, []*Commit{c2}, c4.GetParents())
assert.Equal(t, []*Commit{c1}, c2.GetParents())
- assert.Equal(t, []*Commit{}, c1.GetParents())
+ assert.Equal(t, []*Commit(nil), c1.GetParents())
assert.Equal(t, []*Commit{c2}, c3.GetParents())
// Ensure that we can start in an empty dir and check out from scratch properly.
@@ -111,8 +116,12 @@
m1 := repo.Get("master")
m2 := repo2.Get("master")
// These will confuse AssertDeepEqual.
- m1.repo = nil
- m2.repo = nil
+ for _, c := range repo.commitsData {
+ c.repo = nil
+ }
+ for _, c := range repo2.commitsData {
+ c.repo = nil
+ }
deepequal.AssertDeepEqual(t, m1, m2)
}
@@ -144,25 +153,29 @@
head := repo.Get("master")
assert.NotNil(t, head)
gotCommits := map[*Commit]bool{}
- assert.NoError(t, head.Recurse(func(c *Commit) (bool, error) {
+ assert.NoError(t, head.Recurse(func(c *Commit) error {
assert.False(t, gotCommits[c])
gotCommits[c] = true
- return true, nil
+ return nil
}))
assert.Equal(t, len(commits), len(gotCommits))
for _, c := range commits {
assert.True(t, gotCommits[c])
}
+ // AllCommits is the same thing as the above.
+ allCommits, err := head.AllCommits()
+ assert.NoError(t, err)
+ assert.Equal(t, len(allCommits), len(gotCommits))
// Verify that we properly return early when the passed-in function
// return false.
gotCommits = map[*Commit]bool{}
- assert.NoError(t, head.Recurse(func(c *Commit) (bool, error) {
+ assert.NoError(t, head.Recurse(func(c *Commit) error {
gotCommits[c] = true
if c == c3 || c == c4 {
- return false, nil
+ return ErrStopRecursing
}
- return true, nil
+ return nil
}))
assert.False(t, gotCommits[c1])
assert.False(t, gotCommits[c2])
@@ -170,12 +183,12 @@
// Verify that we properly exit immediately when the passed-in function
// returns an error.
gotCommits = map[*Commit]bool{}
- assert.Error(t, head.Recurse(func(c *Commit) (bool, error) {
+ assert.Error(t, head.Recurse(func(c *Commit) error {
gotCommits[c] = true
if c == c4 {
- return false, fmt.Errorf("STOP!")
+ return fmt.Errorf("STOP!")
}
- return true, nil
+ return nil
}))
assert.False(t, gotCommits[c1])
assert.False(t, gotCommits[c2])
@@ -196,10 +209,10 @@
test := func() {
gotCommits := map[*Commit]bool{}
- assert.NoError(t, repo.RecurseAllBranches(func(c *Commit) (bool, error) {
+ assert.NoError(t, repo.RecurseAllBranches(func(c *Commit) error {
assert.False(t, gotCommits[c])
gotCommits[c] = true
- return true, nil
+ return nil
}))
assert.Equal(t, len(commits), len(gotCommits))
for _, c := range commits {
@@ -229,27 +242,27 @@
// Verify that we still stop recursion when requested.
gotCommits := map[*Commit]bool{}
- assert.NoError(t, repo.RecurseAllBranches(func(c *Commit) (bool, error) {
+ assert.NoError(t, repo.RecurseAllBranches(func(c *Commit) error {
gotCommits[c] = true
if c == c3 || c == c4 {
- return false, nil
+ return ErrStopRecursing
}
- return true, nil
+ return nil
}))
assert.False(t, gotCommits[c1])
assert.False(t, gotCommits[c2])
// Verify that we error out properly.
gotCommits = map[*Commit]bool{}
- assert.Error(t, repo.RecurseAllBranches(func(c *Commit) (bool, error) {
+ assert.Error(t, repo.RecurseAllBranches(func(c *Commit) error {
gotCommits[c] = true
// Because of nondeterministic map iteration and the added
// branches, we have to halt way back at c2 in order to have
// a sane, deterministic test case.
if c == c2 {
- return false, fmt.Errorf("STOP!")
+ return fmt.Errorf("STOP!")
}
- return true, nil
+ return nil
}))
assert.False(t, gotCommits[c1])
assert.True(t, gotCommits[c2])
@@ -346,9 +359,9 @@
assert.NoError(t, err)
assert.False(t, anc)
- assert.NoError(t, c6.Recurse(func(c *Commit) (bool, error) {
+ assert.NoError(t, c6.Recurse(func(c *Commit) error {
assert.NotEqual(t, c, c3)
- return true, nil
+ return nil
}))
}
@@ -465,3 +478,365 @@
assert.Equal(t, 1, len(newCommits))
assert.Equal(t, mergeCommit, newCommits[0].Hash)
}
+
+func TestRevList(t *testing.T) {
+ testutils.MediumTest(t)
+
+ ctx := context.Background()
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ commits := git_testutils.GitSetup(ctx, gb)
+
+ tmpDir, err := ioutil.TempDir("", "")
+ assert.NoError(t, err)
+ defer testutils.RemoveAll(t, tmpDir)
+ d1 := path.Join(tmpDir, "1")
+ assert.NoError(t, os.Mkdir(d1, os.ModePerm))
+ co, err := git.NewCheckout(ctx, gb.Dir(), d1)
+ assert.NoError(t, err)
+ d2 := path.Join(tmpDir, "2")
+ assert.NoError(t, os.Mkdir(d2, os.ModePerm))
+ g, err := NewGraph(ctx, gb.Dir(), d2)
+ assert.NoError(t, err)
+ assert.NoError(t, g.Update(ctx))
+
+ check := func(from, to string, expectOrig []string) {
+ expect := util.CopyStringSlice(expectOrig)
+ revs, err := co.RevList(ctx, fmt.Sprintf("%s..%s", from, to))
+ assert.NoError(t, err)
+ // Sanity check; assert that the commits returned from git are
+ // in reverse topological order.
+ assertHashesTopoSorted(t, g, revs)
+
+ // Topological sorting is not deterministic, so we can't compare
+ // the slices directly.
+ sort.Strings(expect)
+ sort.Strings(revs)
+ deepequal.AssertDeepEqual(t, expect, revs)
+
+ revs, err = g.RevList(from, to)
+ assert.NoError(t, err)
+ assertHashesTopoSorted(t, g, revs)
+ sort.Strings(revs)
+ deepequal.AssertDeepEqual(t, expect, revs)
+ }
+
+ check(commits[0], commits[4], commits[1:])
+ check(commits[1], commits[2], commits[2:3])
+ check(commits[1], commits[4], commits[2:5])
+ check(commits[2], commits[4], commits[3:5])
+ check(commits[3], commits[4], []string{commits[2], commits[4]})
+}
+
+// Assert that the given Commits are in reverse topological order.
+func assertTopoSorted(t *testing.T, commits []*Commit) {
+ // Collect all commits in a map so we can quickly check for their
+ // presence in the commits slice.
+ commitsMap := make(map[*Commit]bool, len(commits))
+ for _, c := range commits {
+ // Assert that each parent is not yet visited.
+ parents := c.GetParents()
+ for _, p := range parents {
+ assert.False(t, commitsMap[p])
+ }
+ commitsMap[c] = true
+ }
+
+ // Ensure that we don't mix lines of history. Practically this means,
+ // for each commit:
+ // - If it has one parent and its parent has one child,
+ // they are adjacent in the sorted slice.
+ // - If it has multiple parents, it is adjacent to one of them.
+ // - If it has multiple children, it is adjacent to one of them.
+
+ // Create a mapping from commits to their children.
+ children := make(map[*Commit]map[*Commit]bool, len(commits))
+ for _, c := range commits {
+ for _, p := range c.parents {
+ // Don't include parents which aren't in the slice.
+ if !commitsMap[p] {
+ continue
+ }
+ subMap, ok := children[p]
+ if !ok {
+ subMap = map[*Commit]bool{}
+ children[p] = subMap
+ }
+ subMap[c] = true
+ }
+ }
+
+ // Verify that the above cases are true for each commit in the slice.
+ for idx, c := range commits {
+ sklog.Errorf("Check %d / %d", idx, len(commits))
+
+ // If the commit has one parent, and its parent has one child,
+ // they should be adjacent.
+ if len(c.parents) == 1 && len(children[c.parents[0]]) == 1 {
+ // Expect that we're not at the end of the commits slice
+ // since the parent should be listed after the current
+ // commit.
+ assert.True(t, len(commits) > idx+1)
+ assert.Equal(t, c.parents[0], commits[idx+1])
+ }
+
+ // If the commit has multiple parents, it should be adjacent to
+ // one of them.
+ if len(c.parents) > 1 {
+ expectParentAdjacent := false
+ parentIsAdjacent := false
+ for _, p := range c.parents {
+ // Only include parents which are in the slice.
+ if commitsMap[p] {
+ expectParentAdjacent = true
+ if len(commits) > idx+1 && commits[idx+1] == p {
+ parentIsAdjacent = true
+ break
+ }
+ }
+ }
+ assert.Equal(t, expectParentAdjacent, parentIsAdjacent)
+ }
+
+ // If the commit has multiple children, it should be adjacent
+ // to one of them.
+ if len(children[c]) > 1 {
+ assert.True(t, idx > 0)
+ childIsAdjacent := false
+ for child, _ := range children[c] {
+ if commits[idx-1] == child {
+ childIsAdjacent = true
+ break
+ }
+ }
+ assert.True(t, childIsAdjacent)
+ }
+ }
+}
+
+// Assert that the given commit hashses are in reverse topological order.
+func assertHashesTopoSorted(t *testing.T, repo *Graph, hashes []string) {
+ commits := make([]*Commit, 0, len(hashes))
+ for _, hash := range hashes {
+ commits = append(commits, repo.Get(hash))
+ }
+ assertTopoSorted(t, commits)
+}
+
+func TestTopoSort(t *testing.T) {
+ testutils.LargeTest(t)
+
+ ctx := context.Background()
+
+ check := func(commits []*Commit) {
+ sorted := TopologicalSort(commits)
+
+ // Ensure that all of the commits are in the resulting slice.
+ assert.Equal(t, len(commits), len(sorted))
+ found := make(map[*Commit]bool, len(commits))
+ for _, c := range sorted {
+ found[c] = true
+ }
+ for _, c := range commits {
+ assert.True(t, found[c])
+ }
+ assertTopoSorted(t, sorted)
+ }
+ checkGraph := func(g *Graph) {
+ sets := util.PowerSet(len(g.commitsData))
+ for _, set := range sets {
+ inp := make([]*Commit, 0, len(g.commitsData))
+ for _, idx := range set {
+ inp = append(inp, g.commitsData[idx])
+ }
+ check(inp)
+ }
+ }
+ checkGitBuilder := func(gb *git_testutils.GitBuilder, expect []string) {
+ tmpDir, err := ioutil.TempDir("", "")
+ assert.NoError(t, err)
+ defer testutils.RemoveAll(t, tmpDir)
+ g, err := NewGraph(ctx, gb.Dir(), tmpDir)
+ assert.NoError(t, err)
+ assert.NoError(t, g.Update(ctx))
+ checkGraph(g)
+
+ // Test stability by verifying that we get the expected ordering
+ // for the entire repo, multiple times.
+ for i := 0; i < 10; i++ {
+ sorted := CommitSlice(TopologicalSort(g.commitsData)).Hashes()
+ deepequal.AssertDeepEqual(t, expect, sorted)
+ }
+ }
+
+ // Test topological sorting using the default test repo.
+ {
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ commits := git_testutils.GitSetup(ctx, gb)
+
+ // GitSetup doesn't wait between commits, which means that the
+ // timestamps might be equal. Adjust the expectations
+ // accordingly.
+ expect := []string{commits[4], commits[3], commits[2], commits[1], commits[0]}
+ tmpDir, err := ioutil.TempDir("", "")
+ assert.NoError(t, err)
+ defer testutils.RemoveAll(t, tmpDir)
+ g, err := NewGraph(ctx, gb.Dir(), tmpDir)
+ assert.NoError(t, err)
+ assert.NoError(t, g.Update(ctx))
+ c3 := g.Get(commits[3])
+ c2 := g.Get(commits[2])
+ if c3.Timestamp.Equal(c2.Timestamp) && c3.Hash < c2.Hash {
+ expect[1], expect[2] = expect[2], expect[1]
+ }
+ checkGitBuilder(gb, expect)
+ }
+
+ // Verify that we use the timestamp as a tie-breaker.
+ {
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ gb.Add(ctx, "file0", "contents")
+ ts := time.Unix(1552403492, 0)
+ c0 := gb.CommitMsgAt(ctx, "Initial commit", ts)
+ assert.Equal(t, c0, "c48b90c8ccc70b4d2bd146e4f708c398f78e2dd6") // Hashes are deterministic.
+ ts = ts.Add(2 * time.Second)
+ gb.Add(ctx, "file1", "contents")
+ c1 := gb.CommitMsgAt(ctx, "Child 1", ts)
+ gb.CreateBranchAtCommit(ctx, "otherbranch", c0)
+ ts = ts.Add(2 * time.Second)
+ gb.Add(ctx, "file2", "contents")
+ c2 := gb.CommitMsgAt(ctx, "Child 2", ts)
+ // c1 and c2 both have c0 as a parent. The topological ordering
+ // is ambiguous unless we use the timestamp as a tie-breaker, in
+ // which case c2 should always come before c1, since it is more
+ // recent.
+ checkGitBuilder(gb, []string{c2, c1, c0})
+ }
+
+ // Same as above, but the child commits have the same timestamp. Verify
+ // that we use the commit hash as a secondary tie-breaker.
+ {
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ gb.Add(ctx, "file0", "contents")
+ ts := time.Unix(1552403492, 0)
+ c0 := gb.CommitMsgAt(ctx, "Initial commit", ts)
+ assert.Equal(t, c0, "c48b90c8ccc70b4d2bd146e4f708c398f78e2dd6") // Hashes are deterministic.
+ ts = ts.Add(2 * time.Second)
+ gb.Add(ctx, "file1", "contents")
+ c1 := gb.CommitMsgAt(ctx, "Child 1", ts)
+ assert.Equal(t, c1, "dc24e5b042cdcf995a182815ef37f659e8ec20cc")
+ gb.CreateBranchAtCommit(ctx, "otherbranch", c0)
+ gb.Add(ctx, "file2", "contents")
+ c2 := gb.CommitMsgAt(ctx, "Child 2", ts)
+ assert.Equal(t, c2, "06d29d7f828723c79c9eba25696111c628ab5a7e")
+ // c1 and c2 both have c0 as a parent, and they both have the
+ // same timestamp. The topological ordering is ambiguous even
+ // with the timestamp as a tie-breaker, so we have to use the
+ // commit hash.
+ checkGitBuilder(gb, []string{c1, c2, c0})
+ }
+
+ // Extend the above to ensure that, in the case of a merge, we follow
+ // the parent with the newer timestamp.
+ {
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ gb.Add(ctx, "file0", "contents")
+ ts := time.Unix(1552403492, 0)
+ c0 := gb.CommitMsgAt(ctx, "Initial commit", ts)
+ assert.Equal(t, c0, "c48b90c8ccc70b4d2bd146e4f708c398f78e2dd6") // Hashes are deterministic.
+ ts = ts.Add(2 * time.Second)
+ gb.Add(ctx, "file1", "contents")
+ c1 := gb.CommitMsgAt(ctx, "Child 1", ts)
+ assert.Equal(t, c1, "dc24e5b042cdcf995a182815ef37f659e8ec20cc")
+ gb.CreateBranchAtCommit(ctx, "otherbranch", c0)
+ gb.Add(ctx, "file2", "contents")
+ c2 := gb.CommitMsgAt(ctx, "Child 2", ts)
+ assert.Equal(t, c2, "06d29d7f828723c79c9eba25696111c628ab5a7e")
+ gb.CheckoutBranch(ctx, "master")
+ c3 := gb.CommitGen(ctx, "file1")
+ ts = ts.Add(10 * time.Second)
+ c4 := gb.CommitGenAt(ctx, "file1", ts)
+ gb.CheckoutBranch(ctx, "otherbranch")
+ c5 := gb.CommitGen(ctx, "file2")
+ c6 := gb.CommitGenAt(ctx, "file2", ts.Add(-time.Second))
+ gb.CheckoutBranch(ctx, "master")
+ c7 := gb.MergeBranch(ctx, "otherbranch")
+ checkGitBuilder(gb, []string{c7, c4, c3, c1, c6, c5, c2, c0})
+ }
+}
+
+func TestIsAncestor(t *testing.T) {
+ testutils.MediumTest(t)
+
+ ctx := context.Background()
+ gb := git_testutils.GitInit(t, ctx)
+ defer gb.Cleanup()
+ commits := git_testutils.GitSetup(ctx, gb)
+
+ tmpDir, err := ioutil.TempDir("", "")
+ assert.NoError(t, err)
+ defer testutils.RemoveAll(t, tmpDir)
+ d1 := path.Join(tmpDir, "1")
+ assert.NoError(t, os.Mkdir(d1, os.ModePerm))
+ co, err := git.NewCheckout(ctx, gb.Dir(), d1)
+ assert.NoError(t, err)
+ d2 := path.Join(tmpDir, "2")
+ assert.NoError(t, os.Mkdir(d2, os.ModePerm))
+ g, err := NewGraph(ctx, gb.Dir(), d2)
+ assert.NoError(t, err)
+ assert.NoError(t, g.Update(ctx))
+
+ sklog.Infof("4. %s", commits[4][:4])
+ sklog.Infof(" | \\")
+ sklog.Infof("3. %s |", commits[3][:4])
+ sklog.Infof(" | |")
+ sklog.Infof(" | 2. %s", commits[2][:4])
+ sklog.Infof(" | |")
+ sklog.Infof(" | /")
+ sklog.Infof("1. %s", commits[1][:4])
+ sklog.Infof(" |")
+ sklog.Infof("0. %s", commits[0][:4])
+
+ check := func(a, b string, expect bool) {
+ // Compare against actual git.
+ got, err := co.IsAncestor(ctx, a, b)
+ assert.NoError(t, err)
+ assert.Equal(t, expect, got)
+ got, err = g.IsAncestor(a, b)
+ assert.NoError(t, err)
+ assert.Equal(t, expect, got)
+ }
+ check(commits[0], commits[0], true)
+ check(commits[0], commits[1], true)
+ check(commits[0], commits[2], true)
+ check(commits[0], commits[3], true)
+ check(commits[0], commits[4], true)
+
+ check(commits[1], commits[0], false)
+ check(commits[1], commits[1], true)
+ check(commits[1], commits[2], true)
+ check(commits[1], commits[3], true)
+ check(commits[1], commits[4], true)
+
+ check(commits[2], commits[0], false)
+ check(commits[2], commits[1], false)
+ check(commits[2], commits[2], true)
+ check(commits[2], commits[3], false)
+ check(commits[2], commits[4], true)
+
+ check(commits[3], commits[0], false)
+ check(commits[3], commits[1], false)
+ check(commits[3], commits[2], false)
+ check(commits[3], commits[3], true)
+ check(commits[3], commits[4], true)
+
+ check(commits[4], commits[0], false)
+ check(commits[4], commits[1], false)
+ check(commits[4], commits[2], false)
+ check(commits[4], commits[3], false)
+ check(commits[4], commits[4], true)
+}
diff --git a/go/git/testutils/git_builder.go b/go/git/testutils/git_builder.go
index ff93215..e636ea4 100644
--- a/go/git/testutils/git_builder.go
+++ b/go/git/testutils/git_builder.go
@@ -45,7 +45,8 @@
g.run(ctx, "git", "init")
g.run(ctx, "git", "remote", "add", "origin", ".")
-
+ g.run(ctx, "git", "config", "--local", "user.name", "test")
+ g.run(ctx, "git", "config", "--local", "user.email", "test@google.com")
return g
}
diff --git a/go/util/util.go b/go/util/util.go
index b10a10d..4130f3b 100644
--- a/go/util/util.go
+++ b/go/util/util.go
@@ -1205,3 +1205,20 @@
return AskForConfirmation(format, args...)
}
}
+
+// PowerSet returns a slice of slices representing the power set of the indices
+// of a slice.
+func PowerSet(n int) [][]int {
+ if n == 0 {
+ return [][]int{[]int{}}
+ }
+ subs := PowerSet(n - 1)
+ addl := [][]int{}
+ for _, sub := range subs {
+ cpy := make([]int, len(sub)+1)
+ copy(cpy, sub)
+ cpy[len(cpy)-1] = n - 1
+ addl = append(addl, cpy)
+ }
+ return append(subs, addl...)
+}
diff --git a/go/util/util_test.go b/go/util/util_test.go
index 2350032..3010ad4 100644
--- a/go/util/util_test.go
+++ b/go/util/util_test.go
@@ -949,3 +949,14 @@
testGt([]string{"a", "b", "d"}, []string{"a", "b", "c", "d"})
testGt([]string{"a", "c", "b"}, []string{"a", "b", "c"})
}
+
+func TestPowerSet(t *testing.T) {
+ testutils.SmallTest(t)
+ test := func(inp int, expect [][]int) {
+ deepequal.AssertDeepEqual(t, expect, PowerSet(inp))
+ }
+ test(0, [][]int{[]int{}})
+ test(1, [][]int{[]int{}, []int{0}})
+ test(2, [][]int{[]int{}, []int{0}, []int{1}, []int{0, 1}})
+ test(3, [][]int{[]int{}, []int{0}, []int{1}, []int{0, 1}, []int{2}, []int{0, 2}, []int{1, 2}, []int{0, 1, 2}})
+}
diff --git a/task_scheduler/go/blacklist/blacklist.go b/task_scheduler/go/blacklist/blacklist.go
index f8c716f..3000b2b1 100644
--- a/task_scheduler/go/blacklist/blacklist.go
+++ b/task_scheduler/go/blacklist/blacklist.go
@@ -154,7 +154,7 @@
if !ok {
return nil, fmt.Errorf("Unknown repo %s", repoName)
}
- commits, err := repo.Repo().RevList(ctx, fmt.Sprintf("%s..%s", startCommit, endCommit))
+ commits, err := repo.RevList(startCommit, endCommit)
if err != nil {
return nil, err
}
diff --git a/task_scheduler/go/db/cache/cache_test.go b/task_scheduler/go/db/cache/cache_test.go
index d5da9ef..4747edf 100644
--- a/task_scheduler/go/db/cache/cache_test.go
+++ b/task_scheduler/go/db/cache/cache_test.go
@@ -991,12 +991,12 @@
})
var firstCommit *repograph.Commit
- err = repo.RecurseAllBranches(func(c *repograph.Commit) (bool, error) {
+ err = repo.RecurseAllBranches(func(c *repograph.Commit) error {
ts, err := grt("a.git", c.Hash)
assert.NoError(t, err)
assert.True(t, c.Timestamp.Equal(ts))
firstCommit = c
- return true, nil
+ return nil
})
assert.NoError(t, err)
diff --git a/task_scheduler/go/scheduling/perftest/perftest.go b/task_scheduler/go/scheduling/perftest/perftest.go
index a5f1a43..7ba7e2b 100644
--- a/task_scheduler/go/scheduling/perftest/perftest.go
+++ b/task_scheduler/go/scheduling/perftest/perftest.go
@@ -265,10 +265,13 @@
repo, err := repograph.NewGraph(ctx, repoName, workdir)
assertNoError(err)
assertNoError(repo.Update(ctx))
- head, err := repo.Repo().RevParse(ctx, "HEAD")
- assertNoError(err)
+ headCommit := repo.Get("master")
+ if headCommit == nil {
+ sklog.Fatal("Could not find HEAD of master.")
+ }
+ head := headCommit.Hash
- commits, err := repo.Repo().RevList(ctx, head)
+ commits, err := repo.Get(head).AllCommits()
assertNoError(err)
assertDeepEqual([]string{head}, commits)
@@ -351,7 +354,7 @@
// Add more commits to the repo.
makeDummyCommits(ctx, repoDir, 200)
- commits, err = repo.Repo().RevList(ctx, fmt.Sprintf("%s..HEAD", head))
+ commits, err = repo.RevList(head, "master")
assertNoError(err)
// Start the profiler.
diff --git a/task_scheduler/go/scheduling/task_scheduler.go b/task_scheduler/go/scheduling/task_scheduler.go
index 78953f3..00d629f 100644
--- a/task_scheduler/go/scheduling/task_scheduler.go
+++ b/task_scheduler/go/scheduling/task_scheduler.go
@@ -550,25 +550,25 @@
var stealFrom *types.Task
// Run the helper function to recurse on commit history.
- if err := revision.Recurse(func(commit *repograph.Commit) (bool, error) {
+ if err := revision.Recurse(func(commit *repograph.Commit) error {
// Determine whether any task already includes this commit.
prev, err := cache.GetTaskForCommit(repoName, commit.Hash, taskName)
if err != nil {
- return false, err
+ return err
}
// If the blamelist is too large, just use a single commit.
if len(commitsBuf) > MAX_BLAMELIST_COMMITS {
commitsBuf = append(commitsBuf[:0], revision)
//sklog.Warningf("Found too many commits for %s @ %s; using single-commit blamelist.", taskName, revision.Hash)
- return false, ERR_BLAMELIST_DONE
+ return ERR_BLAMELIST_DONE
}
// If we're stealing commits from a previous task but the current
// commit is not in any task's blamelist, we must have scrolled past
// the beginning of the tasks. Just return.
if prev == nil && stealFrom != nil {
- return false, nil
+ return repograph.ErrStopRecursing
}
// If a previous task already included this commit, we have to make a decision.
@@ -589,17 +589,17 @@
for _, c := range stealFrom.Commits {
ptr := repo.Get(c)
if ptr == nil {
- return false, fmt.Errorf("No such commit: %q", c)
+ return fmt.Errorf("No such commit: %q", c)
}
commitsBuf = append(commitsBuf, ptr)
}
- return false, ERR_BLAMELIST_DONE
+ return ERR_BLAMELIST_DONE
}
}
if stealFrom == nil || prev.Id != stealFrom.Id {
// If we've hit a commit belonging to a different task,
// we're done.
- return false, nil
+ return repograph.ErrStopRecursing
}
}
@@ -613,11 +613,11 @@
}
if newTasks[rs][taskName] {
sklog.Infof("Task Spec %s was added in %s; stopping blamelist calculation.", taskName, commit.Hash)
- return false, nil
+ return repograph.ErrStopRecursing
}
// Recurse on the commit's parents.
- return true, nil
+ return nil
}); err != nil && err != ERR_BLAMELIST_DONE {
return nil, nil, err
@@ -1349,7 +1349,7 @@
// recurseAllBranches runs the given func on every commit on all branches, with
// some Task Scheduler-specific exceptions.
-func (s *TaskScheduler) RecurseAllBranches(ctx context.Context, fn func(string, *repograph.Graph, *repograph.Commit) (bool, error)) error {
+func (s *TaskScheduler) RecurseAllBranches(ctx context.Context, fn func(string, *repograph.Graph, *repograph.Commit) error) error {
for repoUrl, r := range s.repos {
blacklistBranches := BRANCH_BLACKLIST[repoUrl]
blacklistCommits := make(map[*repograph.Commit]string, len(blacklistBranches))
@@ -1359,22 +1359,22 @@
blacklistCommits[c] = b
}
}
- if err := r.RecurseAllBranches(func(c *repograph.Commit) (bool, error) {
+ if err := r.RecurseAllBranches(func(c *repograph.Commit) error {
if blacklistBranch, ok := blacklistCommits[c]; ok {
sklog.Infof("Skipping blacklisted branch %q", blacklistBranch)
- return false, nil
+ return repograph.ErrStopRecursing
}
for head, blacklistBranch := range blacklistCommits {
- isAncestor, err := r.Repo().IsAncestor(ctx, c.Hash, head.Hash)
+ isAncestor, err := r.IsAncestor(c.Hash, head.Hash)
if err != nil {
- return false, err
+ return err
} else if isAncestor {
sklog.Infof("Skipping blacklisted branch %q (--is-ancestor)", blacklistBranch)
- return false, nil
+ return repograph.ErrStopRecursing
}
}
if !s.window.TestCommit(repoUrl, c) {
- return false, nil
+ return repograph.ErrStopRecursing
}
return fn(repoUrl, r, c)
}); err != nil {
@@ -1390,10 +1390,10 @@
// Find all new Jobs for all new commits.
newJobs := []*types.Job{}
- if err := s.RecurseAllBranches(ctx, func(repoUrl string, r *repograph.Graph, c *repograph.Commit) (bool, error) {
+ if err := s.RecurseAllBranches(ctx, func(repoUrl string, r *repograph.Graph, c *repograph.Commit) error {
// If this commit isn't in scheduling range, stop recursing.
if !s.window.TestCommit(repoUrl, c) {
- return false, nil
+ return repograph.ErrStopRecursing
}
rs := types.RepoState{
@@ -1408,9 +1408,9 @@
// TODO(borenet): We should consider canceling the jobs
// (how?) since we can never fulfill them.
sklog.Errorf("Failed to obtain new jobs due to permanent error: %s", err)
- return true, nil
+ return nil
}
- return false, err
+ return err
}
alreadyScheduledAllJobs := true
for name, spec := range cfg.Jobs {
@@ -1419,9 +1419,9 @@
if spec.Trigger == specs.TRIGGER_ANY_BRANCH {
shouldRun = true
} else if spec.Trigger == specs.TRIGGER_MASTER_ONLY {
- isAncestor, err := r.Repo().IsAncestor(ctx, c.Hash, "master")
+ isAncestor, err := r.IsAncestor(c.Hash, "master")
if err != nil {
- return false, err
+ return err
} else if isAncestor {
shouldRun = true
}
@@ -1430,7 +1430,7 @@
if shouldRun {
prevJobs, err := s.jCache.GetJobsByRepoState(name, rs)
if err != nil {
- return false, err
+ return err
}
alreadyScheduled := false
for _, prev := range prevJobs {
@@ -1454,7 +1454,7 @@
sklog.Errorf("Got ErrNoSuchEntry after a successful call to GetOrCacheRepoState! Job %s; RepoState: %+v", name, rs)
continue
}
- return false, err
+ return err
}
newJobs = append(newJobs, j)
}
@@ -1464,15 +1464,15 @@
// stop recursing, under the assumption that we've already
// scheduled all of the jobs for the ones before it.
if alreadyScheduledAllJobs {
- return false, nil
+ return repograph.ErrStopRecursing
}
if c.Hash == "50537e46e4f0999df0a4707b227000cfa8c800ff" {
// Stop recursing here, since Jobs were added
// in this commit and previous commits won't be
// valid.
- return false, nil
+ return repograph.ErrStopRecursing
}
- return true, nil
+ return nil
}); err != nil {
return err
}
@@ -1489,12 +1489,12 @@
func (s *TaskScheduler) updateAddedTaskSpecs(ctx context.Context) error {
defer metrics2.FuncTimer().Stop()
repoStates := []types.RepoState{}
- if err := s.RecurseAllBranches(ctx, func(repoUrl string, r *repograph.Graph, c *repograph.Commit) (bool, error) {
+ if err := s.RecurseAllBranches(ctx, func(repoUrl string, r *repograph.Graph, c *repograph.Commit) error {
repoStates = append(repoStates, types.RepoState{
Repo: repoUrl,
Revision: c.Hash,
})
- return true, nil
+ return nil
}); err != nil {
return err
}
diff --git a/task_scheduler/go/scheduling/task_scheduler_test.go b/task_scheduler/go/scheduling/task_scheduler_test.go
index 215c753..6812eb5 100644
--- a/task_scheduler/go/scheduling/task_scheduler_test.go
+++ b/task_scheduler/go/scheduling/task_scheduler_test.go
@@ -1878,13 +1878,12 @@
// Add some commits.
makeDummyCommits(ctx, gb, 10)
- assert.NoError(t, s.repos[gb.RepoUrl()].Repo().Update(ctx))
- commits, err := s.repos[gb.RepoUrl()].Repo().RevList(ctx, "HEAD")
+ assert.NoError(t, s.repos[gb.RepoUrl()].Update(ctx))
+ commits, err := s.repos[gb.RepoUrl()].Get("master").AllCommits()
assert.NoError(t, err)
// Run one task. Ensure that it's at tip-of-tree.
- head, err := s.repos[gb.RepoUrl()].Repo().RevParse(ctx, "HEAD")
- assert.NoError(t, err)
+ head := s.repos[gb.RepoUrl()].Get("master").Hash
swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
assert.NoError(t, s.updateRepos(ctx))
assert.NoError(t, s.MainLoop(ctx))
@@ -2071,8 +2070,7 @@
assert.NoError(t, s.MainLoop(ctx))
assert.NoError(t, s.tCache.Update())
assert.Equal(t, 0, len(s.queue))
- head, err := s.repos[gb.RepoUrl()].Repo().RevParse(ctx, "HEAD")
- assert.NoError(t, err)
+ head := s.repos[gb.RepoUrl()].Get("master").Hash
tasks, err := s.tCache.GetTasksForCommits(gb.RepoUrl(), []string{head})
assert.NoError(t, err)
assert.Equal(t, 1, len(tasks[head]))
@@ -2081,8 +2079,8 @@
// Add some commits to the repo.
gb.CheckoutBranch(ctx, "master")
makeDummyCommits(ctx, gb, 8)
- assert.NoError(t, s.repos[gb.RepoUrl()].Repo().Update(ctx))
- commits, err := s.repos[gb.RepoUrl()].Repo().RevList(ctx, fmt.Sprintf("%s..HEAD", head))
+ assert.NoError(t, s.repos[gb.RepoUrl()].Update(ctx))
+ commits, err := s.repos[gb.RepoUrl()].RevList(head, "master")
assert.Nil(t, err)
assert.Equal(t, 8, len(commits))
assert.NoError(t, s.updateRepos(ctx)) // Most tests want this.
@@ -2866,7 +2864,7 @@
// Add some commits to test blamelist calculation.
makeDummyCommits(ctx, gb, 7)
assert.NoError(t, s.updateRepos(ctx))
- hashes, err := s.repos[gb.RepoUrl()].Repo().RevList(ctx, "HEAD")
+ hashes, err := s.repos[gb.RepoUrl()].Get("master").AllCommits()
assert.NoError(t, err)
return ctx, gb, hashes, d, s, func() {
diff --git a/task_scheduler/go/syncer/syncer.go b/task_scheduler/go/syncer/syncer.go
index a35c39f..6f22359 100644
--- a/task_scheduler/go/syncer/syncer.go
+++ b/task_scheduler/go/syncer/syncer.go
@@ -86,7 +86,7 @@
rvErr <- fmt.Errorf("Unknown repo: %s", rs.Repo)
return
}
- gr, err = tempGitRepo(ctx, repo.Repo(), rs)
+ gr, err = tempGitRepo(ctx, repo, rs)
}
if err != nil {
rvErr <- err
@@ -197,7 +197,7 @@
// tempGitRepo creates a git repository in a temporary directory, gets it into
// the given RepoState, and returns its location.
-func tempGitRepo(ctx context.Context, repo *git.Repo, rs types.RepoState) (rv *git.TempCheckout, rvErr error) {
+func tempGitRepo(ctx context.Context, repo *repograph.Graph, rs types.RepoState) (rv *git.TempCheckout, rvErr error) {
defer metrics2.FuncTimer().Stop()
if rs.IsTryJob() {
diff --git a/task_scheduler/go/testutils/swarming_helpers.go b/task_scheduler/go/testutils/swarming_helpers.go
index 4317b70..415031c 100644
--- a/task_scheduler/go/testutils/swarming_helpers.go
+++ b/task_scheduler/go/testutils/swarming_helpers.go
@@ -23,16 +23,11 @@
botId := 0
rv := []*swarming_api.SwarmingRpcsBotInfo{}
for _, repo := range repos {
- branches, err := repo.Repo().Branches(ctx)
- if err != nil {
- sklog.Error(err)
- continue
- }
- for _, branch := range branches {
+ for _, branch := range repo.BranchHeads() {
if branch.Name != "master" {
continue
}
- contents, err := repo.Repo().GetFile(ctx, specs.TASKS_CFG_FILE, branch.Head)
+ contents, err := repo.GetFile(ctx, specs.TASKS_CFG_FILE, branch.Head)
if err != nil {
sklog.Error(err)
continue
diff --git a/task_scheduler/go/tryjobs/tryjobs_test.go b/task_scheduler/go/tryjobs/tryjobs_test.go
index 49ca48a..a2f6674 100644
--- a/task_scheduler/go/tryjobs/tryjobs_test.go
+++ b/task_scheduler/go/tryjobs/tryjobs_test.go
@@ -144,7 +144,7 @@
}
func TestGetRevision(t *testing.T) {
- ctx, trybots, _, mock, cleanup := setup(t)
+ _, trybots, _, mock, cleanup := setup(t)
defer cleanup()
// Get the (only) commit from the repo.
@@ -153,8 +153,7 @@
}
_, r, _, err := trybots.getRepo(props)
assert.NoError(t, err)
- c, err := r.Repo().RevParse(ctx, "master")
- assert.NoError(t, err)
+ c := r.Get("master").Hash
// Fake response from Gerrit.
ci := &gerrit.ChangeInfo{