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>
This commit is contained in:
Lunny Xiao 2023-06-29 18:03:20 +08:00 committed by GitHub
parent 5a871932f0
commit 6e19484f4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1416 additions and 724 deletions

View file

@ -15,7 +15,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) {
return
}
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) {
branchName := ctx.Params("*")
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
return
}
// check whether branches of this repository has been synced
totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
if ctx.Repo.Repository.IsArchived {
ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository"))
return
}
if ctx.Repo.Repository.IsMirror {
ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
return
}
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
switch {
case git.IsErrBranchNotExist(err):
@ -203,14 +236,14 @@ func CreateBranch(ctx *context.APIContext) {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if models.IsErrBranchDoesNotExist(err) {
if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
}
if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")
} else if models.IsErrBranchNameConflict(err) {
} else if git_model.IsErrBranchNameConflict(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
} else {
ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) {
return
}
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/BranchList"
var totalNumOfBranches int
var totalNumOfBranches int64
var apiBranches []*api.Branch
listOptions := utils.GetListOptions(ctx)
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
}
var err error
totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
return
}
skip, _ := listOptions.GetStartEnd()
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
branches, err := git_model.FindBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) {
apiBranches = make([]*api.Branch, 0, len(branches))
for i := range branches {
c, err := branches[i].GetCommit()
c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
if err != nil {
// Skip if this branch doesn't exist anymore.
if git.IsErrNotExist(err) {
total--
totalNumOfBranches--
continue
}
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) {
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
apiBranches = append(apiBranches, apiBranch)
}
totalNumOfBranches = total
}
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.SetTotalCountHeader(totalNumOfBranches)
ctx.JSON(http.StatusOK, apiBranches)
}
@ -580,7 +629,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
}()
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName)
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return
@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return