Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache last commit when pushing for big repository #10109

Merged
merged 12 commits into from
Oct 8, 2020
7 changes: 4 additions & 3 deletions modules/git/commit_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
return nil, nil, err
}
if len(unHitPaths) > 0 {
revs2, err := getLastCommitForPaths(c, treePath, unHitPaths)
revs2, err := GetLastCommitForPaths(c, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
Expand All @@ -53,7 +53,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
}
}
} else {
revs, err = getLastCommitForPaths(c, treePath, entryPaths)
revs, err = GetLastCommitForPaths(c, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -170,7 +170,8 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac
return results, unHitEntryPaths, nil
}

func getLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
// GetLastCommitForPaths returns last commit information
func GetLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
// We do a tree traversal with nodes sorted by commit time
heap := binaryheap.NewWith(func(a, b interface{}) int {
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
Expand Down
2 changes: 1 addition & 1 deletion modules/git/notes.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func GetNote(repo *Repository, commitID string, note *Note) error {
return err
}

lastCommits, err := getLastCommitForPaths(commitNode, "", []string{path})
lastCommits, err := GetLastCommitForPaths(commitNode, "", []string{path})
if err != nil {
return err
}
Expand Down
97 changes: 97 additions & 0 deletions modules/repository/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repository

import (
"path"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"

cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
)

func recusiveCache(gitRepo *git.Repository, c cgobject.CommitNode, tree *git.Tree, treePath string, ca *cache.LastCommitCache, level int) error {
if level == 0 {
return nil
}

entries, err := tree.ListEntries()
if err != nil {
return err
}

entryPaths := make([]string, len(entries))
entryMap := make(map[string]*git.TreeEntry)
for i, entry := range entries {
entryPaths[i] = entry.Name()
entryMap[entry.Name()] = entry
}

commits, err := git.GetLastCommitForPaths(c, treePath, entryPaths)
if err != nil {
return err
}

for entry, cm := range commits {
if err := ca.Put(c.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil {
return err
}
if entryMap[entry].IsDir() {
subTree, err := tree.SubTree(entry)
if err != nil {
return err
}
if err := recusiveCache(gitRepo, c, subTree, entry, ca, level-1); err != nil {
return err
}
}
}

return nil
}

func getRefName(fullRefName string) string {
if strings.HasPrefix(fullRefName, git.TagPrefix) {
return fullRefName[len(git.TagPrefix):]
} else if strings.HasPrefix(fullRefName, git.BranchPrefix) {
return fullRefName[len(git.BranchPrefix):]
}
return ""
}

// CacheRef cachhe last commit information of the branch or the tag
func CacheRef(repo *models.Repository, gitRepo *git.Repository, fullRefName string) error {
if !setting.CacheService.LastCommit.Enabled {
return nil
}

commit, err := gitRepo.GetCommit(fullRefName)
if err != nil {
return err
}

commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(getRefName(fullRefName), true), commit.CommitsCount)
if err != nil {
return err
}
if commitsCount < setting.CacheService.LastCommit.CommitsCount {
return nil
}

commitNodeIndex, _ := gitRepo.CommitNodeIndex()

c, err := commitNodeIndex.Get(commit.ID)
if err != nil {
return err
}

ca := cache.NewLastCommitCache(repo.FullName(), gitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()))

return recusiveCache(gitRepo, c, &commit.Tree, "", ca, 1)
}
53 changes: 46 additions & 7 deletions services/repository/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type PushUpdateOptions struct {
PusherName string
RepoUserName string
RepoName string
RefFullName string
RefFullName string // branch, tag or other name to push
OldCommitID string
NewCommitID string
}
Expand Down Expand Up @@ -95,11 +95,36 @@ func (opts PushUpdateOptions) BranchName() string {
return opts.RefFullName[len(git.BranchPrefix):]
}

// RefName returns simple name for ref
func (opts PushUpdateOptions) RefName() string {
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
return opts.RefFullName[len(git.TagPrefix):]
} else if strings.HasPrefix(opts.RefFullName, git.BranchPrefix) {
return opts.RefFullName[len(git.BranchPrefix):]
}
return ""
}

// RepoFullName returns repo full name
func (opts PushUpdateOptions) RepoFullName() string {
return opts.RepoUserName + "/" + opts.RepoName
}

// isForcePush detect if a push is a force push
func isForcePush(repoPath string, opts *PushUpdateOptions) (bool, error) {
if !opts.IsUpdateBranch() {
return false, nil
}

output, err := git.NewCommand("rev-list", "--max-count=1", opts.OldCommitID, "^"+opts.NewCommitID).RunInDir(repoPath)
if err != nil {
return false, err
} else if len(output) > 0 {
return true, nil
}
return false, nil
}

// pushQueue represents a queue to handle update pull request tests
var pushQueue queue.Queue

Expand Down Expand Up @@ -184,7 +209,6 @@ func pushUpdates(optsList []*PushUpdateOptions) error {
if opts.IsDelRef() {
delTags = append(delTags, tagName)
} else { // is new tag
cache.Remove(repo.GetCommitsCountCacheKey(tagName, true))
addTags = append(addTags, tagName)
}
} else if opts.IsBranch() { // If is branch reference
Expand All @@ -197,8 +221,8 @@ func pushUpdates(optsList []*PushUpdateOptions) error {

branch := opts.BranchName()
if !opts.IsDelRef() {
// Clear cache for branch commit count
cache.Remove(repo.GetCommitsCountCacheKey(opts.BranchName(), true))
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)

newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
if err != nil {
Expand All @@ -217,6 +241,20 @@ func pushUpdates(optsList []*PushUpdateOptions) error {
if err != nil {
return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err)
}

isForce, err := isForcePush(repo.RepoPath(), opts)
if err != nil {
log.Error("isForcePush %s/%s failed: %v", repo.ID, branch, err)
}

if isForce {
log.Trace("Push %s is a force push", opts.NewCommitID)

cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
} else {
// TODO: increment update the commit count cache but not remove
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
}
}

commits = repo_module.ListToPushCommits(l)
Expand All @@ -225,9 +263,10 @@ func pushUpdates(optsList []*PushUpdateOptions) error {
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
}

log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)

go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
// Cache for big repository
if err := repo_module.CacheRef(repo, gitRepo, opts.RefFullName); err != nil {
log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
}
} else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil {
// close all related pulls
log.Error("close related pull request failed: %v", err)
Expand Down