Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Fargher <jfargher@gitlab.com>2021-08-31 01:33:50 +0300
committerJames Fargher <jfargher@gitlab.com>2021-09-20 00:58:00 +0300
commit3060e008e31b1e227986c81e3c9197e3b484af9d (patch)
tree1aa6cf5f5ae2ddec0ef8aaf01dff15c39494f7da
parentc8f1f9ba89901b08514242dabcde049a61b4c58f (diff)
Implement GetDefaultBranch in localrepo
This logic is required in a few different places and currently lives within the refs service code. To allow better code sharing the ref service code has been extracted.
-rw-r--r--internal/git/localrepo/refs.go91
-rw-r--r--internal/git/localrepo/refs_test.go109
2 files changed, 200 insertions, 0 deletions
diff --git a/internal/git/localrepo/refs.go b/internal/git/localrepo/refs.go
index ac9c69fd1..3f6b87e89 100644
--- a/internal/git/localrepo/refs.go
+++ b/internal/git/localrepo/refs.go
@@ -266,3 +266,94 @@ func (repo *Repo) GetRemoteReferences(ctx context.Context, remote string, opts .
return refs, nil
}
+
+// GetDefaultBranchOptions are options passed to GetDefaultBranch
+type GetDefaultBranchOptions struct {
+ // HeadReference allows specifying the HEAD symbolic reference instead of
+ // looking it up if it is already known
+ HeadReference git.ReferenceName
+}
+
+// GetDefaultBranch determines the default branch name
+func (repo *Repo) GetDefaultBranch(ctx context.Context, opts *GetDefaultBranchOptions) (git.Reference, error) {
+ if opts == nil {
+ opts = &GetDefaultBranchOptions{}
+ }
+
+ branches, err := repo.GetBranches(ctx)
+ if err != nil {
+ return git.Reference{}, err
+ }
+ switch len(branches) {
+ case 0:
+ return git.Reference{}, nil
+ case 1:
+ return branches[0], nil
+ }
+
+ if len(opts.HeadReference) == 0 {
+ var err error
+ opts.HeadReference, err = repo.headReference(ctx)
+ if err != nil {
+ return git.Reference{}, err
+ }
+ }
+
+ var defaultRef, legacyDefaultRef git.Reference
+ for _, branch := range branches {
+ if len(opts.HeadReference) != 0 && opts.HeadReference == branch.Name {
+ return branch, nil
+ }
+
+ if string(git.DefaultRef) == branch.Name.String() {
+ defaultRef = branch
+ }
+
+ if string(git.LegacyDefaultRef) == branch.Name.String() {
+ legacyDefaultRef = branch
+ }
+ }
+
+ if len(defaultRef.Name) != 0 {
+ return defaultRef, nil
+ }
+
+ if len(legacyDefaultRef.Name) != 0 {
+ return legacyDefaultRef, nil
+ }
+
+ // If all else fails, return the first branch name
+ return branches[0], nil
+}
+
+func (repo *Repo) headReference(ctx context.Context) (git.ReferenceName, error) {
+ var headRef []byte
+
+ cmd, err := repo.Exec(ctx, git.SubCmd{
+ Name: "rev-parse",
+ Flags: []git.Option{git.Flag{Name: "--symbolic-full-name"}},
+ Args: []string{"HEAD"},
+ })
+ if err != nil {
+ return "", err
+ }
+
+ scanner := bufio.NewScanner(cmd)
+ scanner.Scan()
+ if err := scanner.Err(); err != nil {
+ return "", err
+ }
+ headRef = scanner.Bytes()
+
+ if err := cmd.Wait(); err != nil {
+ // If the ref pointed at by HEAD doesn't exist, the rev-parse fails
+ // returning the string `"HEAD"`, so we return `nil` without error.
+ if bytes.Equal(headRef, []byte("HEAD")) {
+ return "", nil
+ }
+
+ return "", err
+ }
+
+ return git.ReferenceName(headRef), nil
+}
diff --git a/internal/git/localrepo/refs_test.go b/internal/git/localrepo/refs_test.go
index a99384e42..8a4fbdc98 100644
--- a/internal/git/localrepo/refs_test.go
+++ b/internal/git/localrepo/refs_test.go
@@ -454,3 +454,112 @@ func TestRepo_UpdateRef(t *testing.T) {
})
}
}
+
+func TestGetDefaultBranch(t *testing.T) {
+ const testOID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"
+
+ for _, tc := range []struct {
+ desc string
+ repo func(t *testing.T) *Repo
+ opts *GetDefaultBranchOptions
+ expectedName git.ReferenceName
+ expectedTarget string
+ }{
+ {
+ desc: "default ref",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, true)
+ oid := gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(), gittest.WithBranch("apple"))
+ gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(oid), gittest.WithBranch("main"))
+ return repo
+ },
+ expectedName: git.ReferenceName(git.DefaultRef),
+ },
+ {
+ desc: "legacy default ref",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, true)
+ oid := gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(), gittest.WithBranch("apple"))
+ gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(oid), gittest.WithBranch("master"))
+ return repo
+ },
+ expectedName: git.ReferenceName(git.LegacyDefaultRef),
+ },
+ {
+ desc: "no branches",
+ repo: func(t *testing.T) *Repo {
+ repo, _ := setupRepo(t, true)
+ return repo
+ },
+ },
+ {
+ desc: "one branch",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, true)
+ gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(), gittest.WithBranch("apple"))
+ return repo
+ },
+ expectedName: git.NewReferenceNameFromBranchName("apple"),
+ },
+ {
+ desc: "no default branches",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, true)
+ oid := gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(), gittest.WithBranch("apple"))
+ gittest.WriteCommit(t, repo.cfg, repoPath, gittest.WithParents(oid), gittest.WithBranch("banana"))
+ return repo
+ },
+ expectedName: git.NewReferenceNameFromBranchName("apple"),
+ },
+ {
+ desc: "test repo default",
+ repo: func(t *testing.T) *Repo {
+ repo, _ := setupRepo(t, false)
+ return repo
+ },
+ expectedName: git.ReferenceName(git.LegacyDefaultRef),
+ },
+ {
+ desc: "test repo HEAD set",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, false)
+ gittest.Exec(t, repo.cfg, "-C", repoPath, "update-ref", "refs/heads/feature", testOID)
+ gittest.Exec(t, repo.cfg, "-C", repoPath, "symbolic-ref", "HEAD", "refs/heads/feature")
+ return repo
+ },
+ expectedName: git.NewReferenceNameFromBranchName("feature"),
+ expectedTarget: testOID,
+ },
+ {
+ desc: "test repo HEAD provided",
+ repo: func(t *testing.T) *Repo {
+ repo, repoPath := setupRepo(t, false)
+ gittest.Exec(t, repo.cfg, "-C", repoPath, "update-ref", "refs/heads/feature", testOID)
+ return repo
+ },
+ opts: &GetDefaultBranchOptions{
+ HeadReference: "refs/heads/feature",
+ },
+ expectedName: git.NewReferenceNameFromBranchName("feature"),
+ expectedTarget: testOID,
+ },
+ } {
+ t.Run(tc.desc, func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ branch, err := tc.repo(t).GetDefaultBranch(ctx, tc.opts)
+ require.NoError(t, err)
+ if len(tc.expectedName) != 0 {
+ require.Equal(t, tc.expectedName, branch.Name)
+ }
+ if len(tc.expectedTarget) != 0 {
+ require.Equal(t, tc.expectedTarget, branch.Target)
+ }
+ if len(tc.expectedName) == 0 && len(tc.expectedTarget) == 0 {
+ require.Empty(t, branch.Name)
+ require.Empty(t, branch.Target)
+ }
+ })
+ }
+}