blob: 2f6a60a8576cc3b28bdc6fca01a2e6fc6b2b5938 [file] [log] [blame]
package repograph
import (
"context"
"sort"
"time"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/gitstore"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/vcsinfo"
)
func updateFromGitStore(ctx context.Context, gs gitstore.GitStore, graph *Graph, lastUpdate, now time.Time) ([]*vcsinfo.LongCommit, error) {
// Retrieve the new commits.
sklog.Info("Updating repograph.Graph...")
from := lastUpdate.Add(-10 * time.Minute)
to := now.Add(time.Second)
indexCommits, err := gs.RangeByTime(ctx, from, to, "")
if err != nil {
return nil, err
}
hashes := make([]string, 0, len(indexCommits))
for _, c := range indexCommits {
hashes = append(hashes, c.Hash)
}
commits, err := gs.Get(ctx, hashes)
if err != nil {
return nil, err
}
commitsMap := make(map[string]*vcsinfo.LongCommit, len(commits))
for _, c := range commits {
commitsMap[c.Hash] = c
}
// Obtain the list of branches.
sklog.Info(" Getting branches...")
newBranches, err := gs.GetBranches(ctx)
if err != nil {
return nil, err
}
// Ignore the empty branch, which contains all commits.
delete(newBranches, "")
newBranchesList := make([]string, 0, len(newBranches))
newBranchesMap := make(map[string]string, len(newBranches))
for branch, bh := range newBranches {
newBranchesList = append(newBranchesList, branch)
newBranchesMap[branch] = bh.Head
}
sort.Strings(newBranchesList)
graph.graphMtx.Lock()
defer graph.graphMtx.Unlock()
oldBranchesMap := make(map[string]string, len(graph.branches))
for _, branch := range graph.branches {
oldBranchesMap[branch.Name] = branch.Head
}
// Load new commits.
var newCommits []*vcsinfo.LongCommit
sklog.Infof(" Loading commits...")
needOrphanCheck := false
for _, branchName := range newBranchesList {
newHead := newBranchesMap[branchName]
oldHead := oldBranchesMap[branchName]
// Shortcut: if the branch is up-to-date, skip it.
if newHead == oldHead {
continue
}
// Trace back in time from the new branch head until we find the
// old branch head, any other commit we already have, or a
// commit with no parents.
toProcess := map[string]bool{newHead: true}
for len(toProcess) > 0 {
// Choose a commit to process.
var c string
for commit, _ := range toProcess {
c = commit
break
}
delete(toProcess, c)
// If we've seen this commit before, we're done.
if c == oldHead {
continue
}
if _, ok := graph.commits[c]; ok {
// If we found a previously-known commit before
// we found the old branch head, then history
// has changed and we need to run the orphan
// check.
needOrphanCheck = true
continue
}
// We haven't seen this commit before; add it to newCommits.
details, ok := commitsMap[c]
if !ok {
sklog.Warningf("Commit %q not found in results; performing explicit lookup.", c)
detailsList, err := gs.Get(ctx, []string{c})
if err != nil {
return nil, err
}
details = detailsList[0]
// Write the details back to commitsMap in case
// this commit is needed by other branches.
commitsMap[c] = details
}
newCommits = append(newCommits, details)
// Add the commit's parent(s) to the toProcess map.
for _, p := range details.Parents {
toProcess[p] = true
}
if len(details.Parents) == 0 && oldHead != "" {
// If we found a commit with no parents and this
// is not a new branch, then we've discovered a
// completely new line of history and need to
// check whether the commits on the old line are
// now orphaned.
needOrphanCheck = true
}
}
}
// Add newCommits in reverse order to ensure that all parents are added
// before their children.
for i := len(newCommits) - 1; i >= 0; i-- {
commit := newCommits[i]
if _, ok := graph.commits[commit.Hash]; !ok {
if err := graph.addCommit(commit); err != nil {
return nil, err
}
}
}
if !needOrphanCheck {
// Check to see whether any branches were deleted.
for branch, _ := range oldBranchesMap {
if _, ok := newBranchesMap[branch]; !ok {
needOrphanCheck = true
break
}
}
}
if needOrphanCheck {
sklog.Warningf("History change detected; checking for orphaned commits.")
visited := make(map[*Commit]bool, len(graph.commits))
for _, newBranchHead := range newBranchesMap {
// Not using Get() because graphMtx is locked.
if err := graph.commits[newBranchHead].recurse(func(c *Commit) error {
return nil
}, visited); err != nil {
return nil, err
}
}
for hash, c := range graph.commits {
if !visited[c] {
sklog.Warningf("Commit %s is orphaned. Removing from the Graph.", hash)
delete(graph.commits, hash)
}
}
}
// Update the rest of the Graph.
graph.gitstoreLastUpdate = now
graph.branches = make([]*git.Branch, 0, len(newBranchesMap))
for name, head := range newBranchesMap {
graph.branches = append(graph.branches, &git.Branch{
Name: name,
Head: head,
})
}
sort.Slice(graph.branches, func(i, j int) bool {
return graph.branches[i].Name < graph.branches[j].Name
})
return newCommits, nil
}
// UpdateFromGitStore updates the Graph from a GitStore.
func (r *Graph) UpdateFromGitStore(ctx context.Context, gs gitstore.GitStore, returnNewCommits bool) ([]*vcsinfo.LongCommit, error) {
now := time.Now()
newGraph := r.ShallowCopy()
newCommits, err := updateFromGitStore(ctx, gs, newGraph, newGraph.gitstoreLastUpdate, now)
if err != nil {
return nil, err
}
r.graphMtx.Lock()
defer r.graphMtx.Unlock()
r.branches = newGraph.branches
r.commits = newGraph.commits
r.gitstoreLastUpdate = newGraph.gitstoreLastUpdate
sklog.Infof(" Done. Graph has %d commits.", len(r.commits))
return newCommits, nil
}