diff --git a/modules/context/repo.go b/modules/context/repo.go index 159fd07d9..b2844c04c 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -584,7 +584,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } ctx.Data["Tags"] = tags - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) + brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) if err != nil { ctx.ServerError("GetBranches", err) return @@ -810,7 +810,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if len(ctx.Params("*")) == 0 { refName = ctx.Repo.Repository.DefaultBranch if !ctx.Repo.GitRepo.IsBranchExist(refName) { - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) + brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) if err != nil { ctx.ServerError("GetBranches", err) return diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 98b1bc8ae..01933d7ad 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -95,7 +95,12 @@ func GetBranchesByPath(path string, skip, limit int) ([]*Branch, int, error) { } defer gitRepo.Close() - brs, countAll, err := gitRepo.GetBranches(skip, limit) + return gitRepo.GetBranches(skip, limit) +} + +// GetBranches returns a slice of *git.Branch +func (repo *Repository) GetBranches(skip, limit int) ([]*Branch, int, error) { + brs, countAll, err := repo.GetBranchNames(skip, limit) if err != nil { return nil, 0, err } @@ -103,9 +108,9 @@ func GetBranchesByPath(path string, skip, limit int) ([]*Branch, int, error) { branches := make([]*Branch, len(brs)) for i := range brs { branches[i] = &Branch{ - Path: path, + Path: repo.Path, Name: brs[i], - gitRepo: gitRepo, + gitRepo: repo, } } diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go index 6bf14b399..d159aafd6 100644 --- a/modules/git/repo_branch_gogit.go +++ b/modules/git/repo_branch_gogit.go @@ -9,6 +9,7 @@ package git import ( + "context" "strings" "github.com/go-git/go-git/v5/plumbing" @@ -52,7 +53,7 @@ func (repo *Repository) IsBranchExist(name string) bool { // GetBranches returns branches from the repository, skipping skip initial branches and // returning at most limit branches, or all branches if limit is 0. -func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) { +func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { var branchNames []string branches, err := repo.gogitRepo.Branches() @@ -79,3 +80,26 @@ func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) { return branchNames, count, nil } + +// WalkReferences walks all the references from the repository +func WalkReferences(ctx context.Context, repoPath string, walkfn func(string) error) (int, error) { + repo, err := OpenRepositoryCtx(ctx, repoPath) + if err != nil { + return 0, err + } + defer repo.Close() + + i := 0 + iter, err := repo.gogitRepo.References() + if err != nil { + return i, err + } + defer iter.Close() + + err = iter.ForEach(func(ref *plumbing.Reference) error { + err := walkfn(string(ref.Name())) + i++ + return err + }) + return i, err +} diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index 1928c7515..55952acda 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -61,14 +61,29 @@ func (repo *Repository) IsBranchExist(name string) bool { return repo.IsReferenceExist(BranchPrefix + name) } -// GetBranches returns branches from the repository, skipping skip initial branches and +// GetBranchNames returns branches from the repository, skipping skip initial branches and // returning at most limit branches, or all branches if limit is 0. -func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) { +func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { return callShowRef(repo.Ctx, repo.Path, BranchPrefix, "--heads", skip, limit) } +// WalkReferences walks all the references from the repository +func WalkReferences(ctx context.Context, repoPath string, walkfn func(string) error) (int, error) { + return walkShowRef(ctx, repoPath, "", 0, 0, walkfn) +} + // callShowRef return refs, if limit = 0 it will not limit func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) { + countAll, err = walkShowRef(ctx, repoPath, arg, skip, limit, func(branchName string) error { + branchName = strings.TrimPrefix(branchName, prefix) + branchNames = append(branchNames, branchName) + + return nil + }) + return +} + +func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, walkfn func(string) error) (countAll int, err error) { stdoutReader, stdoutWriter := io.Pipe() defer func() { _ = stdoutReader.Close() @@ -77,7 +92,11 @@ func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit go func() { stderrBuilder := &strings.Builder{} - err := NewCommandContext(ctx, "show-ref", arg).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder) + args := []string{"show-ref"} + if arg != "" { + args = append(args, arg) + } + err := NewCommandContext(ctx, args...).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder) if err != nil { if stderrBuilder.Len() == 0 { _ = stdoutWriter.Close() @@ -94,10 +113,10 @@ func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit for i < skip { _, isPrefix, err := bufReader.ReadLine() if err == io.EOF { - return branchNames, i, nil + return i, nil } if err != nil { - return nil, 0, err + return 0, err } if !isPrefix { i++ @@ -112,39 +131,42 @@ func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit _, err = bufReader.ReadSlice(' ') } if err == io.EOF { - return branchNames, i, nil + return i, nil } if err != nil { - return nil, 0, err + return 0, err } branchName, err := bufReader.ReadString('\n') if err == io.EOF { // This shouldn't happen... but we'll tolerate it for the sake of peace - return branchNames, i, nil + return i, nil } if err != nil { - return nil, i, err + return i, err } - branchName = strings.TrimPrefix(branchName, prefix) + if len(branchName) > 0 { branchName = branchName[:len(branchName)-1] } - branchNames = append(branchNames, branchName) + err = walkfn(branchName) + if err != nil { + return i, err + } i++ } // count all refs for limit != 0 { _, isPrefix, err := bufReader.ReadLine() if err == io.EOF { - return branchNames, i, nil + return i, nil } if err != nil { - return nil, 0, err + return 0, err } if !isPrefix { i++ } } - return branchNames, i, nil + return i, nil } diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 05d5237e6..ac5f5deea 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -17,21 +17,21 @@ func TestRepository_GetBranches(t *testing.T) { assert.NoError(t, err) defer bareRepo1.Close() - branches, countAll, err := bareRepo1.GetBranches(0, 2) + branches, countAll, err := bareRepo1.GetBranchNames(0, 2) assert.NoError(t, err) assert.Len(t, branches, 2) assert.EqualValues(t, 3, countAll) assert.ElementsMatch(t, []string{"branch1", "branch2"}, branches) - branches, countAll, err = bareRepo1.GetBranches(0, 0) + branches, countAll, err = bareRepo1.GetBranchNames(0, 0) assert.NoError(t, err) assert.Len(t, branches, 3) assert.EqualValues(t, 3, countAll) assert.ElementsMatch(t, []string{"branch1", "branch2", "master"}, branches) - branches, countAll, err = bareRepo1.GetBranches(5, 1) + branches, countAll, err = bareRepo1.GetBranchNames(5, 1) assert.NoError(t, err) assert.Len(t, branches, 0) @@ -48,7 +48,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) { defer bareRepo1.Close() for i := 0; i < b.N; i++ { - _, _, err := bareRepo1.GetBranches(0, 0) + _, _, err := bareRepo1.GetBranchNames(0, 0) if err != nil { b.Fatal(err) } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 05b45eba4..9c2518059 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -165,14 +165,14 @@ func redirect(ctx *context.Context) { // loadBranches loads branches from the repository limited by page & pageSize. // NOTE: May write to context on error. func loadBranches(ctx *context.Context, skip, limit int) ([]*Branch, int) { - defaultBranch, err := repo_service.GetBranch(ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch) + defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch) if err != nil { log.Error("loadBranches: get default branch: %v", err) ctx.ServerError("GetDefaultBranch", err) return nil, 0 } - rawBranches, totalNumOfBranches, err := repo_service.GetBranches(ctx.Repo.Repository, skip, limit) + rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit) if err != nil { log.Error("GetBranches: %v", err) ctx.ServerError("GetBranches", err) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 54d7e77f2..4cd817a39 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -660,7 +660,7 @@ func getBranchesAndTagsForRepo(repo *models.Repository) (branches, tags []string } defer gitRepo.Close() - branches, _, err = gitRepo.GetBranches(0, 0) + branches, _, err = gitRepo.GetBranchNames(0, 0) if err != nil { return nil, nil, err } @@ -711,7 +711,7 @@ func CompareDiff(ctx *context.Context) { return } - headBranches, _, err := ci.HeadGitRepo.GetBranches(0, 0) + headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) if err != nil { ctx.ServerError("GetBranches", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index f0857b18c..398aa26cc 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -690,7 +690,7 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull boo return nil } - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) + brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) if err != nil { ctx.ServerError("GetBranches", err) return nil diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 3f4045a77..5503155ab 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -142,7 +142,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r repo.DefaultBranch = strings.TrimPrefix(repo.DefaultBranch, git.BranchPrefix) } - branches, _, _ := gitRepo.GetBranches(0, 0) + branches, _, _ := gitRepo.GetBranchNames(0, 0) found := false hasDefault := false hasMaster := false diff --git a/services/repository/branch.go b/services/repository/branch.go index f33bac762..08310134b 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -5,8 +5,10 @@ package repository import ( + "context" "errors" "fmt" + "strings" "code.gitea.io/gitea/models" user_model "code.gitea.io/gitea/models/user" @@ -20,7 +22,7 @@ import ( // CreateNewBranch creates a new repository branch func CreateNewBranch(doer *user_model.User, repo *models.Repository, oldBranchName, branchName string) (err error) { // Check if branch name can be used - if err := checkBranchName(repo, branchName); err != nil { + if err := checkBranchName(git.DefaultContext, repo, branchName); err != nil { return err } @@ -65,44 +67,39 @@ func GetBranches(repo *models.Repository, skip, limit int) ([]*git.Branch, int, } // checkBranchName validates branch name with existing repository branches -func checkBranchName(repo *models.Repository, name string) error { - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - return err - } - defer gitRepo.Close() - - branches, _, err := GetBranches(repo, 0, 0) - if err != nil { - return err - } - - for _, branch := range branches { - if branch.Name == name { +func checkBranchName(ctx context.Context, repo *models.Repository, name string) error { + _, err := git.WalkReferences(ctx, repo.RepoPath(), func(refName string) error { + branchRefName := strings.TrimPrefix(refName, git.BranchPrefix) + switch { + case branchRefName == name: return models.ErrBranchAlreadyExists{ - BranchName: branch.Name, + BranchName: name, } - } else if (len(branch.Name) < len(name) && branch.Name+"/" == name[0:len(branch.Name)+1]) || - (len(branch.Name) > len(name) && name+"/" == branch.Name[0:len(name)+1]) { + // 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{ - BranchName: branch.Name, + 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{ + BranchName: branchRefName, + } + case refName == git.TagPrefix+name: + return models.ErrTagAlreadyExists{ + TagName: name, } } - } - - if _, err := gitRepo.GetTag(name); err == nil { - return models.ErrTagAlreadyExists{ - TagName: name, - } - } + return nil + }) - return nil + return err } // CreateNewBranchFromCommit creates a new repository branch func CreateNewBranchFromCommit(doer *user_model.User, repo *models.Repository, commit, branchName string) (err error) { // Check if branch name can be used - if err := checkBranchName(repo, branchName); err != nil { + if err := checkBranchName(git.DefaultContext, repo, branchName); err != nil { return err }