1package git
2
3import (
4 "path/filepath"
5 "slices"
6 "testing"
7
8 gogit "github.com/go-git/go-git/v5"
9 "github.com/go-git/go-git/v5/plumbing"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 "github.com/stretchr/testify/suite"
13
14 "tangled.org/core/sets"
15)
16
17type BranchSuite struct {
18 suite.Suite
19 *RepoSuite
20}
21
22func TestBranchSuite(t *testing.T) {
23 t.Parallel()
24 suite.Run(t, new(BranchSuite))
25}
26
27func (s *BranchSuite) SetupTest() {
28 s.RepoSuite = NewRepoSuite(s.T())
29}
30
31func (s *BranchSuite) TearDownTest() {
32 s.RepoSuite.cleanup()
33}
34
35func (s *BranchSuite) setupRepoWithBranches() {
36 s.init()
37
38 // get the initial commit on master
39 head, err := s.repo.r.Head()
40 require.NoError(s.T(), err)
41 initialCommit := head.Hash()
42
43 // create multiple branches with commits
44 // branch-1
45 s.createBranch("branch-1", initialCommit)
46 s.checkoutBranch("branch-1")
47 _ = s.commitFile("file1.txt", "content 1", "Add file1 on branch-1")
48
49 // branch-2
50 s.createBranch("branch-2", initialCommit)
51 s.checkoutBranch("branch-2")
52 _ = s.commitFile("file2.txt", "content 2", "Add file2 on branch-2")
53
54 // branch-3
55 s.createBranch("branch-3", initialCommit)
56 s.checkoutBranch("branch-3")
57 _ = s.commitFile("file3.txt", "content 3", "Add file3 on branch-3")
58
59 // branch-4
60 s.createBranch("branch-4", initialCommit)
61 s.checkoutBranch("branch-4")
62 s.commitFile("file4.txt", "content 4", "Add file4 on branch-4")
63
64 // back to master and make a commit
65 s.checkoutBranch("master")
66 s.commitFile("master-file.txt", "master content", "Add file on master")
67
68 // verify we have multiple branches
69 refs, err := s.repo.r.References()
70 require.NoError(s.T(), err)
71
72 branchCount := 0
73 err = refs.ForEach(func(ref *plumbing.Reference) error {
74 if ref.Name().IsBranch() {
75 branchCount++
76 }
77 return nil
78 })
79 require.NoError(s.T(), err)
80
81 // we should have 5 branches: master, branch-1, branch-2, branch-3, branch-4
82 assert.Equal(s.T(), 5, branchCount, "expected 5 branches")
83}
84
85func (s *BranchSuite) TestBranches_All() {
86 s.setupRepoWithBranches()
87
88 branches, err := s.repo.Branches(&BranchesOptions{})
89 require.NoError(s.T(), err)
90
91 assert.Len(s.T(), branches, 5, "expected 5 branches")
92
93 expectedBranches := sets.Collect(slices.Values([]string{
94 "master",
95 "branch-1",
96 "branch-2",
97 "branch-3",
98 "branch-4",
99 }))
100
101 for _, branch := range branches {
102 assert.True(s.T(), expectedBranches.Contains(branch.Reference.Name),
103 "unexpected branch: %s", branch.Reference.Name)
104 assert.NotEmpty(s.T(), branch.Reference.Hash, "branch hash should not be empty")
105 assert.NotNil(s.T(), branch.Commit, "branch commit should not be nil")
106 }
107}
108
109func (s *BranchSuite) TestBranches_WithLimit() {
110 s.setupRepoWithBranches()
111
112 tests := []struct {
113 name string
114 limit int
115 expectedCount int
116 }{
117 {
118 name: "limit 1",
119 limit: 1,
120 expectedCount: 1,
121 },
122 {
123 name: "limit 2",
124 limit: 2,
125 expectedCount: 2,
126 },
127 {
128 name: "limit 3",
129 limit: 3,
130 expectedCount: 3,
131 },
132 {
133 name: "limit 10 (more than available)",
134 limit: 10,
135 expectedCount: 5,
136 },
137 }
138
139 for _, tt := range tests {
140 s.Run(tt.name, func() {
141 branches, err := s.repo.Branches(&BranchesOptions{
142 Limit: tt.limit,
143 })
144 require.NoError(s.T(), err)
145 assert.Len(s.T(), branches, tt.expectedCount, "expected %d branches", tt.expectedCount)
146 })
147 }
148}
149
150func (s *BranchSuite) TestBranches_WithOffset() {
151 s.setupRepoWithBranches()
152
153 tests := []struct {
154 name string
155 offset int
156 expectedCount int
157 }{
158 {
159 name: "offset 0",
160 offset: 0,
161 expectedCount: 5,
162 },
163 {
164 name: "offset 1",
165 offset: 1,
166 expectedCount: 4,
167 },
168 {
169 name: "offset 2",
170 offset: 2,
171 expectedCount: 3,
172 },
173 {
174 name: "offset 4",
175 offset: 4,
176 expectedCount: 1,
177 },
178 {
179 name: "offset 5 (all skipped)",
180 offset: 5,
181 expectedCount: 0,
182 },
183 {
184 name: "offset 10 (more than available)",
185 offset: 10,
186 expectedCount: 0,
187 },
188 }
189
190 for _, tt := range tests {
191 s.Run(tt.name, func() {
192 branches, err := s.repo.Branches(&BranchesOptions{
193 Offset: tt.offset,
194 })
195 require.NoError(s.T(), err)
196 assert.Len(s.T(), branches, tt.expectedCount, "expected %d branches", tt.expectedCount)
197 })
198 }
199}
200
201func (s *BranchSuite) TestBranches_WithLimitAndOffset() {
202 s.setupRepoWithBranches()
203
204 tests := []struct {
205 name string
206 limit int
207 offset int
208 expectedCount int
209 }{
210 {
211 name: "limit 2, offset 0",
212 limit: 2,
213 offset: 0,
214 expectedCount: 2,
215 },
216 {
217 name: "limit 2, offset 1",
218 limit: 2,
219 offset: 1,
220 expectedCount: 2,
221 },
222 {
223 name: "limit 2, offset 3",
224 limit: 2,
225 offset: 3,
226 expectedCount: 2,
227 },
228 {
229 name: "limit 2, offset 4",
230 limit: 2,
231 offset: 4,
232 expectedCount: 1,
233 },
234 {
235 name: "limit 3, offset 2",
236 limit: 3,
237 offset: 2,
238 expectedCount: 3,
239 },
240 {
241 name: "limit 10, offset 3",
242 limit: 10,
243 offset: 3,
244 expectedCount: 2,
245 },
246 }
247
248 for _, tt := range tests {
249 s.Run(tt.name, func() {
250 branches, err := s.repo.Branches(&BranchesOptions{
251 Limit: tt.limit,
252 Offset: tt.offset,
253 })
254 require.NoError(s.T(), err)
255 assert.Len(s.T(), branches, tt.expectedCount, "expected %d branches", tt.expectedCount)
256 })
257 }
258}
259
260func (s *BranchSuite) TestBranches_EmptyRepo() {
261 repoPath := filepath.Join(s.tempDir, "empty-repo")
262
263 _, err := gogit.PlainInit(repoPath, false)
264 require.NoError(s.T(), err)
265
266 gitRepo, err := PlainOpen(repoPath)
267 require.NoError(s.T(), err)
268
269 branches, err := gitRepo.Branches(&BranchesOptions{})
270 require.NoError(s.T(), err)
271
272 if branches != nil {
273 assert.Empty(s.T(), branches, "expected no branches in empty repo")
274 }
275}
276
277func (s *BranchSuite) TestBranches_Pagination() {
278 s.setupRepoWithBranches()
279
280 allBranches, err := s.repo.Branches(&BranchesOptions{})
281 require.NoError(s.T(), err)
282 assert.Len(s.T(), allBranches, 5, "expected 5 branches")
283
284 pageSize := 2
285 var paginatedBranches []string
286
287 for offset := 0; offset < len(allBranches); offset += pageSize {
288 branches, err := s.repo.Branches(&BranchesOptions{
289 Limit: pageSize,
290 Offset: offset,
291 })
292 require.NoError(s.T(), err)
293 for _, branch := range branches {
294 paginatedBranches = append(paginatedBranches, branch.Reference.Name)
295 }
296 }
297
298 assert.Len(s.T(), paginatedBranches, len(allBranches), "pagination should return all branches")
299
300 // create sets to verify all branches are present
301 allBranchNames := sets.New[string]()
302 for _, branch := range allBranches {
303 allBranchNames.Insert(branch.Reference.Name)
304 }
305
306 paginatedBranchNames := sets.New[string]()
307 for _, name := range paginatedBranches {
308 paginatedBranchNames.Insert(name)
309 }
310
311 assert.EqualValues(s.T(), allBranchNames, paginatedBranchNames,
312 "pagination should return the same set of branches")
313}
314
315func (s *BranchSuite) TestBranches_VerifyBranchFields() {
316 s.setupRepoWithBranches()
317
318 branches, err := s.repo.Branches(&BranchesOptions{})
319 require.NoError(s.T(), err)
320
321 found := false
322 for i := range branches {
323 if branches[i].Reference.Name == "master" {
324 found = true
325 assert.Equal(s.T(), "master", branches[i].Reference.Name)
326 assert.NotEmpty(s.T(), branches[i].Reference.Hash)
327 assert.NotNil(s.T(), branches[i].Commit)
328 assert.NotEmpty(s.T(), branches[i].Commit.Author.Name)
329 assert.NotEmpty(s.T(), branches[i].Commit.Author.Email)
330 assert.False(s.T(), branches[i].Commit.Hash.IsZero())
331 break
332 }
333 }
334
335 assert.True(s.T(), found, "master branch not found")
336}
337
338func (s *BranchSuite) TestBranches_NilOptions() {
339 s.setupRepoWithBranches()
340
341 branches, err := s.repo.Branches(nil)
342 require.NoError(s.T(), err)
343 assert.Len(s.T(), branches, 5, "nil options should return all branches")
344}
345
346func (s *BranchSuite) TestBranches_ZeroLimitAndOffset() {
347 s.setupRepoWithBranches()
348
349 branches, err := s.repo.Branches(&BranchesOptions{
350 Limit: 0,
351 Offset: 0,
352 })
353 require.NoError(s.T(), err)
354 assert.Len(s.T(), branches, 5, "zero limit should return all branches")
355}