Skip to content

Commit 8f1ce6e

Browse files
zmotsoMykolaMarusenko
authored andcommitted
fix: Incomplete branch list for Bitbucket (#41)
1 parent ed55447 commit 8f1ce6e

File tree

8 files changed

+214
-43
lines changed

8 files changed

+214
-43
lines changed

internal/services/bitbucket/bitbucket.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
gferrors "github.com/KubeRocketCI/gitfusion/internal/errors"
1010
"github.com/KubeRocketCI/gitfusion/internal/models"
1111
"github.com/KubeRocketCI/gitfusion/internal/services/krci"
12+
bitbucketpkg "github.com/KubeRocketCI/gitfusion/pkg/bitbucket"
1213
"github.com/ktrysmt/go-bitbucket"
1314
)
1415

@@ -124,16 +125,23 @@ func (b *BitbucketService) ListBranches(
124125
branchOptions := &bitbucket.RepositoryBranchOptions{
125126
Owner: owner,
126127
RepoSlug: repo,
128+
Pagelen: 100,
127129
}
128130

129-
branchesResp, err := client.Repositories.Repository.ListBranches(branchOptions)
130-
if err != nil {
131-
return nil, fmt.Errorf("failed to list branches for %s/%s: %w", owner, repo, err)
132-
}
131+
scanBranches := bitbucketpkg.ScanBitbucketBranches(
132+
func(rbo *bitbucket.RepositoryBranchOptions) (*bitbucket.RepositoryBranches, error) {
133+
return client.Repositories.Repository.ListBranches(rbo)
134+
},
135+
branchOptions,
136+
)
133137

134-
result := make([]models.Branch, 0, len(branchesResp.Branches))
138+
result := make([]models.Branch, 0)
139+
140+
for b, err := range scanBranches {
141+
if err != nil {
142+
return nil, fmt.Errorf("failed to list branches: %w", err)
143+
}
135144

136-
for _, b := range branchesResp.Branches {
137145
result = append(result, models.Branch{
138146
Name: b.Name,
139147
})

internal/services/github/github.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"iter"
87
"net/http"
98
"strconv"
109
"strings"
@@ -14,6 +13,7 @@ import (
1413
"github.com/KubeRocketCI/gitfusion/internal/services/krci"
1514
gfgithub "github.com/KubeRocketCI/gitfusion/pkg/github"
1615
"github.com/KubeRocketCI/gitfusion/pkg/pointer"
16+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
1717
"github.com/google/go-github/v72/github"
1818
"golang.org/x/sync/errgroup"
1919
)
@@ -80,11 +80,11 @@ func (g *GitHubProvider) ListRepositories(
8080
}
8181

8282
func filterRepositoriesByName(
83-
it iter.Seq2[*github.Repository, error],
83+
scan xiter.Scan[*github.Repository],
8484
opt models.ListOptions,
85-
) iter.Seq2[*github.Repository, error] {
85+
) xiter.Scan[*github.Repository] {
8686
return func(yield func(*github.Repository, error) bool) {
87-
it(func(repo *github.Repository, err error) bool {
87+
scan(func(repo *github.Repository, err error) bool {
8888
if err != nil {
8989
return yield(nil, err)
9090
}
@@ -113,7 +113,7 @@ func (g *GitHubProvider) listRepositories(
113113
ctx context.Context,
114114
owner string,
115115
client *github.Client,
116-
) iter.Seq2[*github.Repository, error] {
116+
) xiter.Scan[*github.Repository] {
117117
_, _, orgErr := client.Organizations.Get(ctx, owner)
118118
if orgErr == nil {
119119
return gfgithub.ScanGitHubList(

internal/services/github/github_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package github
22

33
import (
4-
"iter"
54
"slices"
65
"testing"
76

87
"github.com/KubeRocketCI/gitfusion/internal/models"
9-
gfgithub "github.com/KubeRocketCI/gitfusion/pkg/github"
8+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
109
"github.com/google/go-github/v72/github"
1110
"github.com/stretchr/testify/assert"
1211
)
1312

1413
func Test_filterProjectsByName(t *testing.T) {
1514
type args struct {
16-
it iter.Seq2[*github.Repository, error]
15+
it xiter.Scan[*github.Repository]
1716
opt models.ListOptions
1817
}
1918

@@ -26,7 +25,7 @@ func Test_filterProjectsByName(t *testing.T) {
2625
{
2726
name: "returns all when Name is nil",
2827
args: args{
29-
it: iter.Seq2[*github.Repository, error](func(yield func(*github.Repository, error) bool) {
28+
it: xiter.Scan[*github.Repository](func(yield func(*github.Repository, error) bool) {
3029
if !yield(&github.Repository{Name: github.Ptr("repo1")}, nil) {
3130
return
3231
}
@@ -43,7 +42,7 @@ func Test_filterProjectsByName(t *testing.T) {
4342
{
4443
name: "filters by substring (case-insensitive)",
4544
args: args{
46-
it: iter.Seq2[*github.Repository, error](func(yield func(*github.Repository, error) bool) {
45+
it: xiter.Scan[*github.Repository](func(yield func(*github.Repository, error) bool) {
4746
if !yield(&github.Repository{Name: github.Ptr("with a")}, nil) {
4847
return
4948
}
@@ -63,7 +62,7 @@ func Test_filterProjectsByName(t *testing.T) {
6362
{
6463
name: "returns empty when no match",
6564
args: args{
66-
it: iter.Seq2[*github.Repository, error](func(yield func(*github.Repository, error) bool) {
65+
it: xiter.Scan[*github.Repository](func(yield func(*github.Repository, error) bool) {
6766
if !yield(&github.Repository{Name: github.Ptr("foo")}, nil) {
6867
return
6968
}
@@ -77,7 +76,7 @@ func Test_filterProjectsByName(t *testing.T) {
7776
{
7877
name: "handles error from iterator",
7978
args: args{
80-
it: iter.Seq2[*github.Repository, error](func(yield func(*github.Repository, error) bool) {
79+
it: xiter.Scan[*github.Repository](func(yield func(*github.Repository, error) bool) {
8180
yield(nil, assert.AnError)
8281
}),
8382
opt: models.ListOptions{Name: nil},
@@ -89,7 +88,7 @@ func Test_filterProjectsByName(t *testing.T) {
8988

9089
for _, tt := range tests {
9190
t.Run(tt.name, func(t *testing.T) {
92-
got, err := gfgithub.CollectFromScan(filterRepositoriesByName(tt.args.it, tt.args.opt))
91+
got, err := xiter.CollectFromScan(filterRepositoriesByName(tt.args.it, tt.args.opt))
9392
assert.True(t, slices.EqualFunc(got, tt.want, func(a, b *github.Repository) bool {
9493
return a.GetName() == b.GetName()
9594
}))

pkg/bitbucket/pagination.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package bitbucket
2+
3+
import (
4+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
5+
bitbucketcl "github.com/ktrysmt/go-bitbucket"
6+
)
7+
8+
// ScanBitbucketBranches scans all branches for a given repository.
9+
func ScanBitbucketBranches(
10+
fetchPage func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error),
11+
rbo *bitbucketcl.RepositoryBranchOptions,
12+
) xiter.Scan[*bitbucketcl.RepositoryBranch] {
13+
return func(yield func(*bitbucketcl.RepositoryBranch, error) bool) {
14+
for {
15+
branchesResp, err := fetchPage(rbo)
16+
if err != nil {
17+
yield(nil, err)
18+
19+
return
20+
}
21+
22+
for _, branch := range branchesResp.Branches {
23+
if !yield(&branch, nil) {
24+
return
25+
}
26+
}
27+
28+
if branchesResp.Next == "" {
29+
break
30+
}
31+
32+
rbo.PageNum = branchesResp.Page + 1
33+
}
34+
}
35+
}

pkg/bitbucket/pagination_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package bitbucket
2+
3+
import (
4+
"errors"
5+
"slices"
6+
"testing"
7+
8+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
9+
bitbucketcl "github.com/ktrysmt/go-bitbucket"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
// helper to create a bitbucketcl.RepositoryBranch with a given name
14+
func branch(name string) bitbucketcl.RepositoryBranch {
15+
return bitbucketcl.RepositoryBranch{Name: name}
16+
}
17+
18+
// helper to create a *bitbucketcl.RepositoryBranches response
19+
func branchesResponse(branches []bitbucketcl.RepositoryBranch, next string, page int) *bitbucketcl.RepositoryBranches {
20+
return &bitbucketcl.RepositoryBranches{
21+
Branches: branches,
22+
Next: next,
23+
Page: page,
24+
}
25+
}
26+
27+
func TestScanBitbucketBranches(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
fetchPage func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error)
31+
rbo *bitbucketcl.RepositoryBranchOptions
32+
want []*bitbucketcl.RepositoryBranch
33+
wantErr assert.ErrorAssertionFunc
34+
}{
35+
{
36+
name: "single page",
37+
fetchPage: func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
38+
return branchesResponse([]bitbucketcl.RepositoryBranch{
39+
branch("main"),
40+
branch("develop"),
41+
}, "", 1), nil
42+
},
43+
rbo: &bitbucketcl.RepositoryBranchOptions{},
44+
want: []*bitbucketcl.RepositoryBranch{
45+
{Name: "main"},
46+
{Name: "develop"},
47+
},
48+
wantErr: assert.NoError,
49+
},
50+
{
51+
name: "multiple pages",
52+
fetchPage: func() func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
53+
pages := map[int]*bitbucketcl.RepositoryBranches{
54+
1: branchesResponse([]bitbucketcl.RepositoryBranch{branch("main")}, "next", 1),
55+
2: branchesResponse([]bitbucketcl.RepositoryBranch{branch("develop")}, "", 2),
56+
}
57+
return func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
58+
if rbo.PageNum == 0 {
59+
rbo.PageNum = 1
60+
}
61+
if resp, ok := pages[rbo.PageNum]; ok {
62+
return resp, nil
63+
}
64+
return branchesResponse([]bitbucketcl.RepositoryBranch{}, "", rbo.PageNum), nil
65+
}
66+
}(),
67+
rbo: &bitbucketcl.RepositoryBranchOptions{},
68+
want: []*bitbucketcl.RepositoryBranch{
69+
{Name: "main"},
70+
{Name: "develop"},
71+
},
72+
wantErr: assert.NoError,
73+
},
74+
{
75+
name: "error on first page",
76+
fetchPage: func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
77+
return nil, errors.New("fetch failed")
78+
},
79+
rbo: &bitbucketcl.RepositoryBranchOptions{},
80+
want: nil,
81+
wantErr: assert.Error,
82+
},
83+
{
84+
name: "error on second page",
85+
fetchPage: func() func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
86+
calls := 0
87+
return func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
88+
calls++
89+
if calls == 1 {
90+
return branchesResponse([]bitbucketcl.RepositoryBranch{branch("main")}, "next", 1), nil
91+
}
92+
return nil, errors.New("fetch failed on second page")
93+
}
94+
}(),
95+
rbo: &bitbucketcl.RepositoryBranchOptions{},
96+
want: []*bitbucketcl.RepositoryBranch{
97+
{Name: "main"},
98+
},
99+
wantErr: assert.Error,
100+
},
101+
{
102+
name: "empty response",
103+
fetchPage: func(rbo *bitbucketcl.RepositoryBranchOptions) (*bitbucketcl.RepositoryBranches, error) {
104+
return branchesResponse([]bitbucketcl.RepositoryBranch{}, "", 1), nil
105+
},
106+
rbo: &bitbucketcl.RepositoryBranchOptions{},
107+
want: []*bitbucketcl.RepositoryBranch{},
108+
wantErr: assert.NoError,
109+
},
110+
}
111+
112+
for _, tt := range tests {
113+
t.Run(tt.name, func(t *testing.T) {
114+
got, err := xiter.CollectFromScan(ScanBitbucketBranches(tt.fetchPage, tt.rbo))
115+
116+
// Compare the results using slices.EqualFunc for proper comparison
117+
assert.True(t, slices.EqualFunc(got, tt.want, func(a, b *bitbucketcl.RepositoryBranch) bool {
118+
return a.Name == b.Name
119+
}))
120+
121+
tt.wantErr(t, err)
122+
})
123+
}
124+
}

pkg/github/pagination.go

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package github
22

33
import (
4-
"iter"
5-
4+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
65
"github.com/google/go-github/v72/github"
76
)
87

@@ -13,7 +12,7 @@ import (
1312
func ScanGitHubList[T any](
1413
fetchPage func(opt github.ListOptions) ([]T, *github.Response, error),
1514
opts ...ScanGitHubListOption,
16-
) iter.Seq2[T, error] {
15+
) xiter.Scan[T] {
1716
return func(yield func(T, error) bool) {
1817
opt := github.ListOptions{PerPage: 100}
1918
for _, o := range opts {
@@ -56,23 +55,3 @@ func WithPerPage(perPage int) ScanGitHubListOption {
5655
}
5756
}
5857
}
59-
60-
// CollectFromScan collects all items and error from an Scan.
61-
func CollectFromScan[T any](it iter.Seq2[T, error]) ([]T, error) {
62-
var items []T
63-
64-
var err error
65-
66-
it(func(item T, e error) bool {
67-
if e != nil {
68-
err = e
69-
return false
70-
}
71-
72-
items = append(items, item)
73-
74-
return true
75-
})
76-
77-
return items, err
78-
}

pkg/github/pagination_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"slices"
66
"testing"
77

8+
"github.com/KubeRocketCI/gitfusion/pkg/xiter"
89
"github.com/google/go-github/v72/github"
910
"github.com/stretchr/testify/assert"
1011
)
@@ -85,7 +86,7 @@ func TestScanGitHubList(t *testing.T) {
8586

8687
for _, tt := range tests {
8788
t.Run(tt.name, func(t *testing.T) {
88-
got, err := CollectFromScan(ScanGitHubList(tt.do, tt.opts))
89+
got, err := xiter.CollectFromScan(ScanGitHubList(tt.do, tt.opts))
8990
assert.True(t, slices.EqualFunc(got, tt.want, func(a, b *github.Repository) bool {
9091
return a.GetName() == b.GetName()
9192
}))

pkg/xiter/iter.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package xiter
2+
3+
import "iter"
4+
5+
type Scan[T any] iter.Seq2[T, error]
6+
7+
// CollectFromScan collects all items and error from an Scan.
8+
func CollectFromScan[T any](it Scan[T]) ([]T, error) {
9+
var items []T
10+
11+
var err error
12+
13+
it(func(item T, e error) bool {
14+
if e != nil {
15+
err = e
16+
return false
17+
}
18+
19+
items = append(items, item)
20+
21+
return true
22+
})
23+
24+
return items, err
25+
}

0 commit comments

Comments
 (0)