forked from
tangled.org/core
Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2
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:subject",
38 "contents:body",
39 "contents:signature",
40 }
41
42 var outFormat strings.Builder
43 outFormat.WriteString("--format=")
44 for i, f := range fields {
45 if i != 0 {
46 outFormat.WriteString(fieldSeparator)
47 }
48 fmt.Fprintf(&outFormat, "%%(%s)", f)
49 }
50 outFormat.WriteString("")
51 outFormat.WriteString(recordSeparator)
52
53 args := []string{outFormat.String(), "--sort=-creatordate"}
54
55 // only add the count if the limit is a non-zero value,
56 // if it is zero, get as many tags as we can
57 if opts.Limit > 0 {
58 args = append(args, fmt.Sprintf("--count=%d", opts.Offset+opts.Limit))
59 }
60
61 args = append(args, opts.Pattern)
62
63 output, err := g.forEachRef(args...)
64 if err != nil {
65 return nil, fmt.Errorf("failed to get tags: %w", err)
66 }
67
68 records := strings.Split(strings.TrimSpace(string(output)), recordSeparator)
69 if len(records) == 1 && records[0] == "" {
70 return nil, nil
71 }
72
73 startIdx := opts.Offset
74 if startIdx >= len(records) {
75 return nil, nil
76 }
77
78 endIdx := len(records)
79 if opts.Limit > 0 {
80 endIdx = min(startIdx+opts.Limit, len(records))
81 }
82
83 records = records[startIdx:endIdx]
84 tags := make([]object.Tag, 0, len(records))
85
86 for _, line := range records {
87 parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, len(fields))
88 if len(parts) < 6 {
89 continue
90 }
91
92 tagName := parts[0]
93 objectHash := parts[1]
94 objectType := parts[2]
95 targetHash := parts[3] // dereferenced object hash (empty for lightweight tags)
96 // targetType := parts[4] // dereferenced object type (empty for lightweight tags)
97 taggerName := parts[5]
98 taggerEmail := parts[6]
99 taggerDate := parts[7]
100 subject := parts[8]
101 body := parts[9]
102 signature := parts[10]
103
104 // combine subject and body for the message
105 var message string
106 if subject != "" && body != "" {
107 message = subject + "\n\n" + body
108 } else if subject != "" {
109 message = subject
110 } else {
111 message = body
112 }
113
114 // parse creation time
115 var createdAt time.Time
116 if unix, err := strconv.ParseInt(taggerDate, 10, 64); err == nil {
117 createdAt = time.Unix(unix, 0)
118 }
119
120 // parse object type
121 typ, err := plumbing.ParseObjectType(objectType)
122 if err != nil {
123 return nil, err
124 }
125
126 // strip email separators
127 taggerEmail = strings.TrimSuffix(strings.TrimPrefix(taggerEmail, "<"), ">")
128
129 tag := object.Tag{
130 Hash: plumbing.NewHash(objectHash),
131 Name: tagName,
132 Tagger: object.Signature{
133 Name: taggerName,
134 Email: taggerEmail,
135 When: createdAt,
136 },
137 Message: message,
138 PGPSignature: signature,
139 TargetType: typ,
140 Target: plumbing.NewHash(targetHash),
141 }
142
143 tags = append(tags, tag)
144 }
145
146 return tags, nil
147}