Signed-off-by: oppiliappan me@oppi.li
+418
-25
Diff
round #0
+38
-3
knotserver/git/tag.go
+38
-3
knotserver/git/tag.go
···
10
10
"github.com/go-git/go-git/v5/plumbing/object"
11
11
)
12
12
13
-
func (g *GitRepo) Tags() ([]object.Tag, error) {
13
+
type TagsOptions struct {
14
+
Limit int
15
+
Offset int
16
+
Pattern string
17
+
}
18
+
19
+
func (g *GitRepo) Tags(opts *TagsOptions) ([]object.Tag, error) {
20
+
if opts == nil {
21
+
opts = &TagsOptions{}
22
+
}
23
+
24
+
if opts.Pattern == "" {
25
+
opts.Pattern = "refs/tags"
26
+
}
27
+
14
28
fields := []string{
15
29
"refname:short",
16
30
"objectname",
···
29
43
if i != 0 {
30
44
outFormat.WriteString(fieldSeparator)
31
45
}
32
-
outFormat.WriteString(fmt.Sprintf("%%(%s)", f))
46
+
fmt.Fprintf(&outFormat, "%%(%s)", f)
33
47
}
34
48
outFormat.WriteString("")
35
49
outFormat.WriteString(recordSeparator)
36
50
37
-
output, err := g.forEachRef(outFormat.String(), "--sort=-creatordate", "refs/tags")
51
+
args := []string{outFormat.String(), "--sort=-creatordate"}
52
+
53
+
// only add the count if the limit is a non-zero value,
54
+
// if it is zero, get as many tags as we can
55
+
if opts.Limit > 0 {
56
+
args = append(args, fmt.Sprintf("--count=%d", opts.Offset+opts.Limit))
57
+
}
58
+
59
+
args = append(args, opts.Pattern)
60
+
61
+
output, err := g.forEachRef(args...)
38
62
if err != nil {
39
63
return nil, fmt.Errorf("failed to get tags: %w", err)
40
64
}
···
44
68
return nil, nil
45
69
}
46
70
71
+
startIdx := opts.Offset
72
+
if startIdx >= len(records) {
73
+
return nil, nil
74
+
}
75
+
76
+
endIdx := len(records)
77
+
if opts.Limit > 0 {
78
+
endIdx = min(startIdx+opts.Limit, len(records))
79
+
}
80
+
81
+
records = records[startIdx:endIdx]
47
82
tags := make([]object.Tag, 0, len(records))
48
83
49
84
for _, line := range records {
+365
knotserver/git/tag_test.go
+365
knotserver/git/tag_test.go
···
1
+
package git
2
+
3
+
import (
4
+
"path/filepath"
5
+
"testing"
6
+
"time"
7
+
8
+
gogit "github.com/go-git/go-git/v5"
9
+
"github.com/go-git/go-git/v5/plumbing"
10
+
"github.com/go-git/go-git/v5/plumbing/object"
11
+
"github.com/stretchr/testify/assert"
12
+
"github.com/stretchr/testify/require"
13
+
"github.com/stretchr/testify/suite"
14
+
)
15
+
16
+
type TagSuite struct {
17
+
suite.Suite
18
+
*RepoSuite
19
+
}
20
+
21
+
func TestTagSuite(t *testing.T) {
22
+
t.Parallel()
23
+
suite.Run(t, new(TagSuite))
24
+
}
25
+
26
+
func (s *TagSuite) SetupTest() {
27
+
s.RepoSuite = NewRepoSuite(s.T())
28
+
}
29
+
30
+
func (s *TagSuite) TearDownTest() {
31
+
s.RepoSuite.cleanup()
32
+
}
33
+
34
+
func (s *TagSuite) setupRepoWithTags() {
35
+
s.init()
36
+
37
+
// create commits for tagging
38
+
commit1 := s.commitFile("file1.txt", "content 1", "Add file1")
39
+
commit2 := s.commitFile("file2.txt", "content 2", "Add file2")
40
+
commit3 := s.commitFile("file3.txt", "content 3", "Add file3")
41
+
commit4 := s.commitFile("file4.txt", "content 4", "Add file4")
42
+
commit5 := s.commitFile("file5.txt", "content 5", "Add file5")
43
+
44
+
// create annotated tags
45
+
s.createAnnotatedTag(
46
+
"v1.0.0",
47
+
commit1,
48
+
"Tagger One",
49
+
"tagger1@example.com",
50
+
"Release version 1.0.0\n\nThis is the first stable release.",
51
+
s.baseTime.Add(1*time.Hour),
52
+
)
53
+
54
+
s.createAnnotatedTag(
55
+
"v1.1.0",
56
+
commit2,
57
+
"Tagger Two",
58
+
"tagger2@example.com",
59
+
"Release version 1.1.0",
60
+
s.baseTime.Add(2*time.Hour),
61
+
)
62
+
63
+
// create lightweight tags
64
+
s.createLightweightTag("v2.0.0", commit3)
65
+
s.createLightweightTag("v2.1.0", commit4)
66
+
67
+
// create another annotated tag
68
+
s.createAnnotatedTag(
69
+
"v3.0.0",
70
+
commit5,
71
+
"Tagger Three",
72
+
"tagger3@example.com",
73
+
"Major version 3.0.0\n\nBreaking changes included.",
74
+
s.baseTime.Add(3*time.Hour),
75
+
)
76
+
}
77
+
78
+
func (s *TagSuite) TestTags_All() {
79
+
s.setupRepoWithTags()
80
+
81
+
tags, err := s.repo.Tags(nil)
82
+
require.NoError(s.T(), err)
83
+
84
+
// we created 5 tags total (3 annotated, 2 lightweight)
85
+
assert.Len(s.T(), tags, 5, "expected 5 tags")
86
+
87
+
// verify tags are sorted by creation date (newest first)
88
+
expectedAnnotated := map[string]bool{
89
+
"v1.0.0": true,
90
+
"v1.1.0": true,
91
+
"v3.0.0": true,
92
+
}
93
+
94
+
expectedLightweight := map[string]bool{
95
+
"v2.0.0": true,
96
+
"v2.1.0": true,
97
+
}
98
+
99
+
for _, tag := range tags {
100
+
if expectedAnnotated[tag.Name] {
101
+
// annotated tags should have tagger info
102
+
assert.NotEmpty(s.T(), tag.Tagger.Name, "annotated tag %s should have tagger name", tag.Name)
103
+
assert.NotEmpty(s.T(), tag.Message, "annotated tag %s should have message", tag.Name)
104
+
} else if expectedLightweight[tag.Name] {
105
+
// lightweight tags won't have tagger info or message (they'll have empty values)
106
+
} else {
107
+
s.T().Errorf("unexpected tag name: %s", tag.Name)
108
+
}
109
+
}
110
+
}
111
+
112
+
func (s *TagSuite) TestTags_WithLimit() {
113
+
s.setupRepoWithTags()
114
+
115
+
tests := []struct {
116
+
name string
117
+
limit int
118
+
expectedCount int
119
+
}{
120
+
{
121
+
name: "limit 1",
122
+
limit: 1,
123
+
expectedCount: 1,
124
+
},
125
+
{
126
+
name: "limit 2",
127
+
limit: 2,
128
+
expectedCount: 2,
129
+
},
130
+
{
131
+
name: "limit 3",
132
+
limit: 3,
133
+
expectedCount: 3,
134
+
},
135
+
{
136
+
name: "limit 10 (more than available)",
137
+
limit: 10,
138
+
expectedCount: 5,
139
+
},
140
+
}
141
+
142
+
for _, tt := range tests {
143
+
s.Run(tt.name, func() {
144
+
tags, err := s.repo.Tags(&TagsOptions{
145
+
Limit: tt.limit,
146
+
})
147
+
require.NoError(s.T(), err)
148
+
assert.Len(s.T(), tags, tt.expectedCount, "expected %d tags", tt.expectedCount)
149
+
})
150
+
}
151
+
}
152
+
153
+
func (s *TagSuite) TestTags_WithOffset() {
154
+
s.setupRepoWithTags()
155
+
156
+
tests := []struct {
157
+
name string
158
+
offset int
159
+
expectedCount int
160
+
}{
161
+
{
162
+
name: "offset 0",
163
+
offset: 0,
164
+
expectedCount: 5,
165
+
},
166
+
{
167
+
name: "offset 1",
168
+
offset: 1,
169
+
expectedCount: 4,
170
+
},
171
+
{
172
+
name: "offset 2",
173
+
offset: 2,
174
+
expectedCount: 3,
175
+
},
176
+
{
177
+
name: "offset 4",
178
+
offset: 4,
179
+
expectedCount: 1,
180
+
},
181
+
{
182
+
name: "offset 5 (all skipped)",
183
+
offset: 5,
184
+
expectedCount: 0,
185
+
},
186
+
{
187
+
name: "offset 10 (more than available)",
188
+
offset: 10,
189
+
expectedCount: 0,
190
+
},
191
+
}
192
+
193
+
for _, tt := range tests {
194
+
s.Run(tt.name, func() {
195
+
tags, err := s.repo.Tags(&TagsOptions{
196
+
Offset: tt.offset,
197
+
})
198
+
require.NoError(s.T(), err)
199
+
assert.Len(s.T(), tags, tt.expectedCount, "expected %d tags", tt.expectedCount)
200
+
})
201
+
}
202
+
}
203
+
204
+
func (s *TagSuite) TestTags_WithLimitAndOffset() {
205
+
s.setupRepoWithTags()
206
+
207
+
tests := []struct {
208
+
name string
209
+
limit int
210
+
offset int
211
+
expectedCount int
212
+
}{
213
+
{
214
+
name: "limit 2, offset 0",
215
+
limit: 2,
216
+
offset: 0,
217
+
expectedCount: 2,
218
+
},
219
+
{
220
+
name: "limit 2, offset 1",
221
+
limit: 2,
222
+
offset: 1,
223
+
expectedCount: 2,
224
+
},
225
+
{
226
+
name: "limit 2, offset 3",
227
+
limit: 2,
228
+
offset: 3,
229
+
expectedCount: 2,
230
+
},
231
+
{
232
+
name: "limit 2, offset 4",
233
+
limit: 2,
234
+
offset: 4,
235
+
expectedCount: 1,
236
+
},
237
+
{
238
+
name: "limit 3, offset 2",
239
+
limit: 3,
240
+
offset: 2,
241
+
expectedCount: 3,
242
+
},
243
+
{
244
+
name: "limit 10, offset 3",
245
+
limit: 10,
246
+
offset: 3,
247
+
expectedCount: 2,
248
+
},
249
+
}
250
+
251
+
for _, tt := range tests {
252
+
s.Run(tt.name, func() {
253
+
tags, err := s.repo.Tags(&TagsOptions{
254
+
Limit: tt.limit,
255
+
Offset: tt.offset,
256
+
})
257
+
require.NoError(s.T(), err)
258
+
assert.Len(s.T(), tags, tt.expectedCount, "expected %d tags", tt.expectedCount)
259
+
})
260
+
}
261
+
}
262
+
263
+
func (s *TagSuite) TestTags_EmptyRepo() {
264
+
repoPath := filepath.Join(s.tempDir, "empty-repo")
265
+
266
+
_, err := gogit.PlainInit(repoPath, false)
267
+
require.NoError(s.T(), err)
268
+
269
+
gitRepo, err := PlainOpen(repoPath)
270
+
require.NoError(s.T(), err)
271
+
272
+
tags, err := gitRepo.Tags(nil)
273
+
require.NoError(s.T(), err)
274
+
275
+
if tags != nil {
276
+
assert.Empty(s.T(), tags, "expected no tags in empty repo")
277
+
}
278
+
}
279
+
280
+
func (s *TagSuite) TestTags_Pagination() {
281
+
s.setupRepoWithTags()
282
+
283
+
allTags, err := s.repo.Tags(nil)
284
+
require.NoError(s.T(), err)
285
+
assert.Len(s.T(), allTags, 5, "expected 5 tags")
286
+
287
+
pageSize := 2
288
+
var paginatedTags []object.Tag
289
+
290
+
for offset := 0; offset < len(allTags); offset += pageSize {
291
+
tags, err := s.repo.Tags(&TagsOptions{
292
+
Limit: pageSize,
293
+
Offset: offset,
294
+
})
295
+
require.NoError(s.T(), err)
296
+
paginatedTags = append(paginatedTags, tags...)
297
+
}
298
+
299
+
assert.Len(s.T(), paginatedTags, len(allTags), "pagination should return all tags")
300
+
301
+
for i := range allTags {
302
+
assert.Equal(s.T(), allTags[i].Name, paginatedTags[i].Name,
303
+
"tag at index %d differs", i)
304
+
}
305
+
}
306
+
307
+
func (s *TagSuite) TestTags_VerifyAnnotatedTagFields() {
308
+
s.setupRepoWithTags()
309
+
310
+
tags, err := s.repo.Tags(nil)
311
+
require.NoError(s.T(), err)
312
+
313
+
var v1Tag *object.Tag
314
+
for i := range tags {
315
+
if tags[i].Name == "v1.0.0" {
316
+
v1Tag = &tags[i]
317
+
break
318
+
}
319
+
}
320
+
321
+
require.NotNil(s.T(), v1Tag, "v1.0.0 tag not found")
322
+
323
+
assert.Equal(s.T(), "Tagger One", v1Tag.Tagger.Name, "tagger name should match")
324
+
assert.Equal(s.T(), "tagger1@example.com", v1Tag.Tagger.Email, "tagger email should match")
325
+
326
+
assert.Equal(s.T(), "Release version 1.0.0\n\nThis is the first stable release.",
327
+
v1Tag.Message, "tag message should match")
328
+
329
+
assert.Equal(s.T(), plumbing.TagObject, v1Tag.TargetType,
330
+
"target type should be CommitObject")
331
+
332
+
assert.False(s.T(), v1Tag.Hash.IsZero(), "tag hash should be set")
333
+
334
+
assert.False(s.T(), v1Tag.Target.IsZero(), "target hash should be set")
335
+
}
336
+
337
+
func (s *TagSuite) TestTags_NilOptions() {
338
+
s.setupRepoWithTags()
339
+
340
+
tags, err := s.repo.Tags(nil)
341
+
require.NoError(s.T(), err)
342
+
assert.Len(s.T(), tags, 5, "nil options should return all tags")
343
+
}
344
+
345
+
func (s *TagSuite) TestTags_ZeroLimitAndOffset() {
346
+
s.setupRepoWithTags()
347
+
348
+
tags, err := s.repo.Tags(&TagsOptions{
349
+
Limit: 0,
350
+
Offset: 0,
351
+
})
352
+
require.NoError(s.T(), err)
353
+
assert.Len(s.T(), tags, 5, "zero limit should return all tags")
354
+
}
355
+
356
+
func (s *TagSuite) TestTags_Pattern() {
357
+
s.setupRepoWithTags()
358
+
359
+
v1tag, err := s.repo.Tags(&TagsOptions{
360
+
Pattern: "refs/tags/v1.0.0",
361
+
})
362
+
363
+
require.NoError(s.T(), err)
364
+
assert.Len(s.T(), v1tag, 1, "expected 1 tag")
365
+
}
History
1 round
0 comments
oppi.li
submitted
#0
1 commit
expand
collapse
knotserver/git: fix pagination in git.Tags
Signed-off-by: oppiliappan <me@oppi.li>
2/3 timeout, 1/3 success
expand
collapse
expand 0 comments
pull request successfully merged