Skip to content

Commit 399de1b

Browse files
authored
Add faster remote branch listing (#232)
* Add faster remote branch listing * Fix test
1 parent 3d206a7 commit 399de1b

7 files changed

Lines changed: 133 additions & 37 deletions

File tree

gitclone/git.go

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,94 @@ type getAvailableBranches func() (map[string][]string, error)
9595

9696
func listBranches(gitCmd git.Git) getAvailableBranches {
9797
return func() (map[string][]string, error) {
98-
if err := runner.Run(gitCmd.Fetch(jobsFlag)); err != nil {
98+
remoteList, err := runner.RunForOutput(gitCmd.RemoteList())
99+
if err != nil {
99100
return nil, err
100101
}
101-
out, err := runner.RunForOutput(gitCmd.Branch("-r"))
102+
103+
remoteBranches, err := runner.RunForOutput(gitCmd.RemoteBranches())
102104
if err != nil {
103105
return nil, err
104106
}
105107

106-
return parseListBranchesOutput(out), nil
108+
return parseListBranchesOutput(remoteList, remoteBranches), nil
107109
}
108110
}
109111

110-
func parseListBranchesOutput(output string) map[string][]string {
111-
lines := strings.Split(output, "\n")
112-
branchesByRemote := map[string][]string{}
113-
for _, line := range lines {
114-
line = strings.Trim(line, " ")
115-
split := strings.Split(line, "/")
116-
117-
remote := split[0]
118-
branch := ""
119-
if len(split) > 1 {
120-
branch = strings.Join(split[1:], "/")
121-
branches := branchesByRemote[remote]
122-
branches = append(branches, branch)
123-
branchesByRemote[remote] = branches
112+
func parseListBranchesOutput(remotes, branches string) map[string][]string {
113+
parsedRemotes := extractRemotes(remotes)
114+
branchesByRemote := extractBranches(parsedRemotes, branches)
115+
116+
return branchesByRemote
117+
}
118+
119+
// extractRemotes parses the output of `git remote -v` and returns a map of remote URLs to their names
120+
// Example of what such an output looks like:
121+
//
122+
// origin git_url1 (fetch)
123+
// origin git_url1 (push)
124+
// upstream git_url2 (fetch)
125+
// upstream git_url2 (push)
126+
//
127+
// (Name of the remote, \t, URL, space, (fetch|push), \n)
128+
func extractRemotes(remotes string) map[string]string {
129+
parsedRemotes := make(map[string]string)
130+
131+
for _, line := range strings.Split(remotes, "\n") {
132+
line = strings.TrimSpace(line)
133+
if line == "" {
134+
continue
135+
}
136+
split := strings.Split(line, "\t")
137+
if len(split) < 2 {
138+
continue
124139
}
140+
141+
name := split[0]
142+
143+
split = strings.Split(split[1], " ")
144+
url := split[0]
145+
146+
parsedRemotes[url] = name
125147
}
148+
149+
return parsedRemotes
150+
}
151+
152+
// extractBranches parses the output of `git ls-remote -b` and returns a map of remote names to their branches
153+
// Example of what such an output looks like:
154+
//
155+
// From git_url
156+
// a50bdc5182e3e7c292f7c8c881a1a0d9476c8eda refs/heads/A
157+
// 25c9d97e5c9e7f4c9ad25597f8e1265af07869d7 refs/heads/B
158+
// 5b3dfe10c52cf5c762740ea5cfa619d82d70e205 refs/heads/C
159+
// f6cdf8f2132f22516bb4d71e326e66a45850eb04 refs/heads/D
160+
// b30b826ac9594330d77554f30103492409035aee refs/heads/master
161+
//
162+
// (From, space, URL, \n)
163+
// (SHA, \t, refs/heads/, branch name, \n)
164+
func extractBranches(remotes map[string]string, branches string) map[string][]string {
165+
branchesByRemote := make(map[string][]string)
166+
currentRemote := ""
167+
for _, line := range strings.Split(branches, "\n") {
168+
if strings.HasPrefix(line, "From ") {
169+
repoURL := strings.TrimPrefix(line, "From ")
170+
currentRemote = remotes[repoURL]
171+
172+
continue
173+
}
174+
175+
split := strings.Split(line, "\t")
176+
if len(split) < 2 || currentRemote == "" {
177+
continue
178+
}
179+
180+
branch := strings.TrimPrefix(split[1], refsHeadsPrefix)
181+
branches := branchesByRemote[currentRemote]
182+
branches = append(branches, branch)
183+
branchesByRemote[currentRemote] = branches
184+
}
185+
126186
return branchesByRemote
127187
}
128188

gitclone/git_test.go

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,63 @@ func Test_getRepo(t *testing.T) {
4747

4848
func Test_parseListBranchesOutput(t *testing.T) {
4949
tests := []struct {
50-
name string
51-
args string
52-
want map[string][]string
50+
name string
51+
remoteList string
52+
branchList string
53+
want map[string][]string
5354
}{
5455
{
55-
name: "single branch",
56-
args: "upstream/master",
56+
name: "single branch, single remote",
57+
remoteList: `
58+
origin git_url (fetch)
59+
origin git_url (push)
60+
`,
61+
branchList: `
62+
From git_url
63+
aaabbbcccddd refs/heads/master
64+
`,
5765
want: map[string][]string{
58-
"upstream": {
66+
"origin": {
5967
"master",
6068
},
6169
},
6270
},
6371
{
64-
name: "multiple branches",
65-
args: `upstream/bitrise-bot-1
66-
upstream/bitrise-bot-2
67-
upstream/bitrise-bot-3`,
72+
name: "multiple branches, multiple remotes",
73+
remoteList: `
74+
origin git_url1 (fetch)
75+
origin git_url1 (push)
76+
upstream git_url2 (fetch)
77+
upstream git_url3 (push)
78+
`,
79+
branchList: `
80+
From git_url1
81+
aaabbbcccddd1 refs/heads/A
82+
aaabbbcccddd2 refs/heads/B
83+
aaabbbcccddd3 refs/heads/C
84+
85+
From git_url2
86+
aaabbbcccddd4 refs/heads/D
87+
aaabbbcccddd5 refs/heads/E
88+
aaabbbcccddd6 refs/heads/F
89+
`,
6890
want: map[string][]string{
91+
"origin": {
92+
"A",
93+
"B",
94+
"C",
95+
},
6996
"upstream": {
70-
"bitrise-bot-1",
71-
"bitrise-bot-2",
72-
"bitrise-bot-3",
97+
"D",
98+
"E",
99+
"F",
73100
},
74101
},
75102
},
76103
}
77104
for _, tt := range tests {
78105
t.Run(tt.name, func(t *testing.T) {
79-
got := parseListBranchesOutput(tt.args)
106+
got := parseListBranchesOutput(tt.remoteList, tt.branchList)
80107
if !reflect.DeepEqual(got, tt.want) {
81108
t.Errorf("parseListBranchesOutput() = %v, want %v", got, tt.want)
82109
}

gitclone/gitclone_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ func Test_checkoutState(t *testing.T) {
200200
`git "fetch" "--jobs=10" "--depth=50" "--no-tags" "origin" "refs/heads/master"`,
201201
`git "fetch" "--jobs=10" "--depth=50" "--no-tags" "origin" "refs/heads/master"`,
202202
`git "fetch" "--jobs=10" "--depth=50" "--no-tags" "origin" "refs/heads/master"`,
203-
`git "fetch" "--jobs=10"`,
204-
`git "branch" "-r"`,
203+
`git "remote" "-v"`,
204+
`git "ls-remote" "-b"`,
205205
},
206206
wantErr: fmt.Errorf("failed to fetch base branch: fetch branch refs/heads/master: dummy_cmd_error: please make sure the branch still exists"),
207207
},
@@ -367,8 +367,8 @@ func Test_checkoutState(t *testing.T) {
367367
`git "fetch" "--jobs=10" "--depth=1" "--no-tags" "--no-recurse-submodules" "origin" "refs/heads/fake"`,
368368
`git "fetch" "--jobs=10" "--depth=1" "--no-tags" "--no-recurse-submodules" "origin" "refs/heads/fake"`,
369369
`git "fetch" "--jobs=10" "--depth=1" "--no-tags" "--no-recurse-submodules" "origin" "refs/heads/fake"`,
370-
`git "fetch" "--jobs=10"`,
371-
`git "branch" "-r"`,
370+
`git "remote" "-v"`,
371+
`git "ls-remote" "-b"`,
372372
},
373373
wantErr: newStepErrorWithBranchRecommendations(
374374
fetchFailedTag,

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/bitrise-io/envman v0.0.0-20210517135508-b2b4fe89eac5
88
github.com/bitrise-io/go-steputils v1.0.5
99
github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.15
10-
github.com/bitrise-io/go-utils v1.0.14
10+
github.com/bitrise-io/go-utils v1.0.15
1111
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.15
1212
github.com/bitrise-steplib/steps-authenticate-host-with-netrc v0.0.0-20230711084209-91fcd09b2017
1313
github.com/hashicorp/go-retryablehttp v0.7.7

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ github.com/bitrise-io/go-utils v1.0.14-0.20241213163208-7104b0101841 h1:UbDuwrjY
2828
github.com/bitrise-io/go-utils v1.0.14-0.20241213163208-7104b0101841/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY=
2929
github.com/bitrise-io/go-utils v1.0.14 h1:hrBriQh47qvy4p3pLkc7gEw9qCWEJYESTJ19ggHRpYs=
3030
github.com/bitrise-io/go-utils v1.0.14/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY=
31+
github.com/bitrise-io/go-utils v1.0.15-0.20250512105156-878e14377860 h1:IBsDGrRbYW8k/dGgh0XWD1wHmACaApssKvzclSxknos=
32+
github.com/bitrise-io/go-utils v1.0.15-0.20250512105156-878e14377860/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY=
33+
github.com/bitrise-io/go-utils v1.0.15 h1:KRQjNiPrkxBRM6G5fQy05v0p0r8wycWfKVb+Ko+Vtg0=
34+
github.com/bitrise-io/go-utils v1.0.15/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY=
3135
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.15 h1:ERQb+OOa+eKMWb+HByyPd5ugz6TeaJPnQ3xHgyaR7hA=
3236
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.15/go.mod h1:Laih4ji980SQkRgdnMCH0g4u2GZI/5nnbqmYT9UfKFQ=
3337
github.com/bitrise-io/go-xcode v0.0.0-20210517092111-792daa927657/go.mod h1:OexOTlMlXuf88bsFsaw5KxmIYrYDsHkTh01vbhGNpus=

vendor/github.com/bitrise-io/go-utils/command/git/commands.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ github.com/bitrise-io/go-steputils/step
1313
## explicit; go 1.17
1414
github.com/bitrise-io/go-steputils/v2/export
1515
github.com/bitrise-io/go-steputils/v2/stepconf
16-
# github.com/bitrise-io/go-utils v1.0.14
16+
# github.com/bitrise-io/go-utils v1.0.15
1717
## explicit; go 1.13
1818
github.com/bitrise-io/go-utils/colorstring
1919
github.com/bitrise-io/go-utils/command

0 commit comments

Comments
 (0)