diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2023-06-29 18:03:20 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-29 10:03:20 +0000 |
commit | 6e19484f4d3bf372212f2da462110a1a8c10cbf2 (patch) | |
tree | e8f1b4920b286241e4ad59151b4f00d9941a27aa /services/repository | |
parent | 5a871932f0efc19a731ee5c1202679653d3cefff (diff) | |
download | gitea-6e19484f4d3bf372212f2da462110a1a8c10cbf2.tar.gz gitea-6e19484f4d3bf372212f2da462110a1a8c10cbf2.zip |
Sync branches into databases (#22743)
Related #14180
Related #25233
Related #22639
Close #19786
Related #12763
This PR will change all the branches retrieve method from reading git
data to read database to reduce git read operations.
- [x] Sync git branches information into database when push git data
- [x] Create a new table `Branch`, merge some columns of `DeletedBranch`
into `Branch` table and drop the table `DeletedBranch`.
- [x] Read `Branch` table when visit `code` -> `branch` page
- [x] Read `Branch` table when list branch names in `code` page dropdown
- [x] Read `Branch` table when list git ref compare page
- [x] Provide a button in admin page to manually sync all branches.
- [x] Sync branches if repository is not empty but database branches are
empty when visiting pages with branches list
- [x] Use `commit_time desc` as the default FindBranch order by to keep
consistent as before and deleted branches will be always at the end.
---------
Co-authored-by: Jason Song <i@wolfogre.com>
Diffstat (limited to 'services/repository')
-rw-r--r-- | services/repository/adopt.go | 11 | ||||
-rw-r--r-- | services/repository/branch.go | 238 | ||||
-rw-r--r-- | services/repository/files/patch.go | 2 | ||||
-rw-r--r-- | services/repository/files/update.go | 2 | ||||
-rw-r--r-- | services/repository/fork.go | 10 | ||||
-rw-r--r-- | services/repository/push.go | 11 | ||||
-rw-r--r-- | services/repository/repository.go | 6 |
7 files changed, 258 insertions, 22 deletions
diff --git a/services/repository/adopt.go b/services/repository/adopt.go index e07ff35041..f95fb5988f 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" @@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } } } - branches, _, _ := gitRepo.GetBranchNames(0, 0) + + branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) + found := false hasDefault := false hasMaster := false diff --git a/services/repository/branch.go b/services/repository/branch.go index 4e560786db..11a8b20531 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,13 +10,21 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/util" + files_service "code.gitea.io/gitea/services/repository/files" + + "xorm.io/builder" ) // CreateNewBranch creates a new repository branch @@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode } if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) { - return models.ErrBranchDoesNotExist{ + return git_model.ErrBranchNotExist{ BranchName: oldBranchName, } } @@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil } -// GetBranches returns branches from the repository, skipping skip initial branches and -// returning at most limit branches, or all branches if limit is 0. -func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) { - return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit) +// Branch contains the branch information +type Branch struct { + DBBranch *git_model.Branch + IsProtected bool + IsIncluded bool + CommitsAhead int + CommitsBehind int + LatestPullRequest *issues_model.PullRequest + MergeMovedOn bool +} + +// LoadBranches loads branches from the repository limited by page & pageSize. +func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) { + defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch) + if err != nil { + return nil, nil, 0, err + } + + branchOpts := git_model.FindBranchOptions{ + RepoID: repo.ID, + IsDeletedBranch: isDeletedBranch, + ListOptions: db.ListOptions{ + Page: page, + PageSize: pageSize, + }, + } + + totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch} + + dbBranches, err := git_model.FindBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + if err := dbBranches.LoadDeletedBy(ctx); err != nil { + return nil, nil, 0, err + } + if err := dbBranches.LoadPusher(ctx); err != nil { + return nil, nil, 0, err + } + + rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) + if err != nil { + return nil, nil, 0, err + } + + repoIDToRepo := map[int64]*repo_model.Repository{} + repoIDToRepo[repo.ID] = repo + + repoIDToGitRepo := map[int64]*git.Repository{} + repoIDToGitRepo[repo.ID] = gitRepo + + branches := make([]*Branch, 0, len(dbBranches)) + for i := range dbBranches { + branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + branches = append(branches, branch) + } + + // Always add the default branch + log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name) + defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + return defaultBranch, branches, totalNumOfBranches, nil +} + +func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules, + repoIDToRepo map[int64]*repo_model.Repository, + repoIDToGitRepo map[int64]*git.Repository, +) (*Branch, error) { + log.Trace("loadOneBranch: '%s'", dbBranch.Name) + + branchName := dbBranch.Name + p := protectedBranches.GetFirstMatched(branchName) + isProtected := p != nil + + divergence := &git.DivergeObject{ + Ahead: -1, + Behind: -1, + } + + // it's not default branch + if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { + var err error + divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName) + if err != nil { + log.Error("CountDivergingCommits: %v", err) + } + } + + pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName) + if err != nil { + return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) + } + headCommit := dbBranch.CommitID + + mergeMovedOn := false + if pr != nil { + pr.HeadRepo = repo + if err := pr.LoadIssue(ctx); err != nil { + return nil, fmt.Errorf("LoadIssue: %v", err) + } + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { + pr.BaseRepo = repo + } else if err := pr.LoadBaseRepo(ctx); err != nil { + return nil, fmt.Errorf("LoadBaseRepo: %v", err) + } else { + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo + } + pr.Issue.Repo = pr.BaseRepo + + if pr.HasMerged { + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] + if !ok { + baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + defer baseGitRepo.Close() + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo + } + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil && !git.IsErrNotExist(err) { + return nil, fmt.Errorf("GetBranchCommitID: %v", err) + } + if err == nil && headCommit != pullCommit { + // the head has moved on from the merge - we shouldn't delete + mergeMovedOn = true + } + } + } + + isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName + return &Branch{ + DBBranch: dbBranch, + IsProtected: isProtected, + IsIncluded: isIncluded, + CommitsAhead: divergence.Ahead, + CommitsBehind: divergence.Behind, + LatestPullRequest: pr, + MergeMovedOn: mergeMovedOn, + }, nil } func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) { @@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri branchRefName := strings.TrimPrefix(refName, git.BranchPrefix) switch { case branchRefName == name: - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: name, } // If branchRefName like a/b but we want to create a branch named a then we have a conflict case strings.HasPrefix(branchRefName, name+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict case strings.HasPrefix(name, branchRefName+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } case refName == git.TagPrefix+name: @@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil @@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return git_model.ErrBranchIsProtected } + rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) + if err != nil { + return fmt.Errorf("GetBranch: %vc", err) + } + + if rawBranch.IsDeleted { + return nil + } + commit, err := gitRepo.GetBranchCommit(branchName) if err != nil { return err } - if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ - Force: true, + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { + return err + } + + return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ + Force: true, + }) }); err != nil { return err } @@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return nil } + +type BranchSyncOptions struct { + RepoID int64 +} + +// branchSyncQueue represents a queue to handle branch sync jobs. +var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions] + +func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions { + for _, opts := range items { + _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0) + if err != nil { + log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err) + } + } + return nil +} + +func addRepoToBranchSyncQueue(repoID, doerID int64) error { + return branchSyncQueue.Push(&BranchSyncOptions{ + RepoID: repoID, + }) +} + +func initBranchSyncQueue(ctx context.Context) error { + branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync) + if branchSyncQueue == nil { + return errors.New("unable to create branch_sync queue") + } + go graceful.GetManager().RunWithCancel(branchSyncQueue) + + return nil +} + +func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error { + if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error { + return addRepoToBranchSyncQueue(repo.ID, doerID) + }); err != nil { + return fmt.Errorf("run sync all branches failed: %v", err) + } + return nil +} diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 19d089b9e4..fdf0b32f1a 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 01bf2ace00..737f914dd6 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return nil, models.ErrBranchAlreadyExists{ + return nil, git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/fork.go b/services/repository/fork.go index fb93b10f1c..59aa173373 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork if err = repo_module.CreateDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) } - return nil + + gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath()) + if err != nil { + return fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) + return err }) needsRollbackInPanic = false if err != nil { diff --git a/services/repository/push.go b/services/repository/push.go index e559d3f904..7e7069f580 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { defer gitRepo.Close() if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) + return fmt.Errorf("Failed to update size for repository: %v", err) } addTags := make([]string, 0, len(optsList)) @@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) - if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) + if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil { + return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) } // Cache for big repository @@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { // close all related pulls log.Error("close related pull request failed: %v", err) } - if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil { - log.Warn("AddDeletedBranch: %v", err) + + if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil { + return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err) } } diff --git a/services/repository/repository.go b/services/repository/repository.go index 0914a8f6ec..cd3658dcd8 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -17,6 +17,7 @@ import ( system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" @@ -100,7 +101,10 @@ func Init() error { } system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) - return initPushQueue() + if err := initPushQueue(); err != nil { + return err + } + return initBranchSyncQueue(graceful.GetManager().ShutdownContext()) } // UpdateRepository updates a repository |