forked from
tangled.org/core
Monorepo for Tangled
1package git
2
3import (
4 "fmt"
5 "strconv"
6 "strings"
7 "time"
8
9 "github.com/go-git/go-git/v5/plumbing"
10 "github.com/go-git/go-git/v5/plumbing/object"
11)
12
13type TagsOptions struct {
14 Limit int
15 Offset int
16 Pattern string
17}
18
19func (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
28 fields := []string{
29 "refname:short",
30 "objectname",
31 "objecttype",
32 "*objectname",
33 "*objecttype",
34 "taggername",
35 "taggeremail",
36 "taggerdate:unix",
37 "contents",
38 }
39
40 var outFormat strings.Builder
41 outFormat.WriteString("--format=")
42 for i, f := range fields {
43 if i != 0 {
44 outFormat.WriteString(fieldSeparator)
45 }
46 fmt.Fprintf(&outFormat, "%%(%s)", f)
47 }
48 outFormat.WriteString("")
49 outFormat.WriteString(recordSeparator)
50
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...)
62 if err != nil {
63 return nil, fmt.Errorf("failed to get tags: %w", err)
64 }
65
66 records := strings.Split(strings.TrimSpace(string(output)), recordSeparator)
67 if len(records) == 1 && records[0] == "" {
68 return nil, nil
69 }
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]
82 tags := make([]object.Tag, 0, len(records))
83
84 for _, line := range records {
85 parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, len(fields))
86 if len(parts) < 6 {
87 continue
88 }
89
90 tagName := parts[0]
91 objectHash := parts[1]
92 objectType := parts[2]
93 targetHash := parts[3] // dereferenced object hash (empty for lightweight tags)
94 // targetType := parts[4] // dereferenced object type (empty for lightweight tags)
95 taggerName := parts[5]
96 taggerEmail := parts[6]
97 taggerDate := parts[7]
98 message := parts[8]
99
100 // parse creation time
101 var createdAt time.Time
102 if unix, err := strconv.ParseInt(taggerDate, 10, 64); err == nil {
103 createdAt = time.Unix(unix, 0)
104 }
105
106 // parse object type
107 typ, err := plumbing.ParseObjectType(objectType)
108 if err != nil {
109 return nil, err
110 }
111
112 // strip email separators
113 taggerEmail = strings.TrimSuffix(strings.TrimPrefix(taggerEmail, "<"), ">")
114
115 tag := object.Tag{
116 Hash: plumbing.NewHash(objectHash),
117 Name: tagName,
118 Tagger: object.Signature{
119 Name: taggerName,
120 Email: taggerEmail,
121 When: createdAt,
122 },
123 Message: message,
124 TargetType: typ,
125 Target: plumbing.NewHash(targetHash),
126 }
127
128 tags = append(tags, tag)
129 }
130
131 return tags, nil
132}