diff options
author | Xing Xin <xingxin.xx@bytedance.com> | 2023-05-04 14:52:22 +0300 |
---|---|---|
committer | James Fargher <jfargher@gitlab.com> | 2023-06-28 01:19:19 +0300 |
commit | 764515237a36e2f5d77f99fc5ddd2e324741599f (patch) | |
tree | f1340045505ec5e85cc1c4ada299bbc688ca2075 | |
parent | 26780b6ce4906b1efd90020ab40560bff5cbec67 (diff) |
localrepo: Avoid getting all branches for default branch detectionblanet_default_branch_opt
To detect the default branch of a repo, we are currently getting all its
branches and compare one by one. This approach is unnecessarily
expensive, especially for repos having too many branches, giving HEAD is
actually available for the vast majority of repos.
This commit adjusts the logic of GetDefaultBranch, we now prefer
detection using git-rev-parse to exhaustion using git-for-each-ref.
For example, gitlab-org/gitlab has around 14,000 branches, getting all
its branches took around 40ms for all refs packed in packed-refs.
$ time git for-each-ref --format="%(refname)%00%(objectname)%00%(symref)" refs/heads/ >/dev/null
real 0m0.042s
user 0m0.032s
sys 0m0.010s
And it's getting terribly bad for all loose-refs.
$ time git for-each-ref --format="%(refname)%00%(objectname)%00%(symref)" refs/heads/ >/dev/null
real 0m0.177s
user 0m0.062s
sys 0m0.111s
But it's much quicker to detect the existence of a branch.
$ time git rev-parse --verify refs/heads/master >/dev/null
real 0m0.003s
user 0m0.002s
sys 0m0.001s
In addition to the comparison of git commands mentioned above, we can
also save more time and memory for not iterating and keeping the
branches list.
Changelog: performance
Signed-off-by: Xing Xin <xingxin.xx@bytedance.com>
-rw-r--r-- | internal/git/localrepo/refs.go | 41 |
1 files changed, 12 insertions, 29 deletions
diff --git a/internal/git/localrepo/refs.go b/internal/git/localrepo/refs.go index d88f95911..dd3248f02 100644 --- a/internal/git/localrepo/refs.go +++ b/internal/git/localrepo/refs.go @@ -326,17 +326,6 @@ func (repo *Repo) GetRemoteReferences(ctx context.Context, remote string, opts . // GetDefaultBranch determines the default branch name func (repo *Repo) GetDefaultBranch(ctx context.Context) (git.ReferenceName, error) { - branches, err := repo.GetBranches(ctx) - if err != nil { - return "", err - } - switch len(branches) { - case 0: - return "", nil - case 1: - return branches[0].Name, nil - } - headReference, err := repo.HeadReference(ctx) if err != nil { return "", err @@ -344,30 +333,24 @@ func (repo *Repo) GetDefaultBranch(ctx context.Context) (git.ReferenceName, erro // Ideally we would only use HEAD to determine the default branch, but // gitlab-rails depends on the branch being determined like this. - var defaultRef, legacyDefaultRef git.ReferenceName - for _, branch := range branches { - if len(headReference) != 0 && headReference == branch.Name { - return branch.Name, nil + for _, headCandidate := range []git.ReferenceName{ + headReference, git.DefaultRef, git.LegacyDefaultRef, + } { + has, err := repo.HasRevision(ctx, headCandidate.Revision()) + if err != nil { + return "", err } - - if git.DefaultRef == branch.Name { - defaultRef = branch.Name + if has { + return headCandidate, nil } - - if git.LegacyDefaultRef == branch.Name { - legacyDefaultRef = branch.Name - } - } - - if len(defaultRef) != 0 { - return defaultRef, nil } - if len(legacyDefaultRef) != 0 { - return legacyDefaultRef, nil + // If all else fails, return the first branch name + branches, err := repo.getReferences(ctx, 1, "refs/heads/") + if err != nil || len(branches) == 0 { + return "", err } - // If all else fails, return the first branch name return branches[0].Name, nil } |