6
6
package git
7
7
8
8
import (
9
+ "fmt"
9
10
"strings"
10
11
11
12
"github.com/mcuadros/go-version"
@@ -35,34 +36,78 @@ func (repo *Repository) CreateTag(name, revision string) error {
35
36
return err
36
37
}
37
38
39
+ // CreateAnnotatedTag create one annotated tag in the repository
40
+ func (repo * Repository ) CreateAnnotatedTag (name , message , revision string ) error {
41
+ _ , err := NewCommand ("tag" , "-a" , "-m" , message , name , revision ).RunInDir (repo .Path )
42
+ return err
43
+ }
44
+
38
45
func (repo * Repository ) getTag (id SHA1 ) (* Tag , error ) {
39
46
t , ok := repo .tagCache .Get (id .String ())
40
47
if ok {
41
48
log ("Hit cache: %s" , id )
42
- return t .(* Tag ), nil
49
+ tagClone := * t .(* Tag )
50
+ return & tagClone , nil
43
51
}
44
52
45
- // Get tag type
46
- tp , err := NewCommand ("cat-file" , "-t" , id .String ()).RunInDir (repo .Path )
53
+ // Get tag name
54
+ name , err := repo .GetTagNameBySHA (id .String ())
55
+ if err != nil {
56
+ return nil , err
57
+ }
58
+
59
+ tp , err := repo .GetTagType (id )
47
60
if err != nil {
48
61
return nil , err
49
62
}
50
- tp = strings .TrimSpace (tp )
51
63
52
- // Tag is a commit.
64
+ // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
65
+ commitIDStr , err := repo .GetTagCommitID (name )
66
+ if err != nil {
67
+ // every tag should have a commit ID so return all errors
68
+ return nil , err
69
+ }
70
+ commitID , err := NewIDFromString (commitIDStr )
71
+ if err != nil {
72
+ return nil , err
73
+ }
74
+
75
+ // tagID defaults to the commit ID as the tag ID and then tries to get a tag ID (only annotated tags)
76
+ tagID := commitID
77
+ if tagIDStr , err := repo .GetTagID (name ); err != nil {
78
+ // if the err is NotExist then we can ignore and just keep tagID as ID (is lightweight tag)
79
+ // all other errors we return
80
+ if ! IsErrNotExist (err ) {
81
+ return nil , err
82
+ }
83
+ } else {
84
+ tagID , err = NewIDFromString (tagIDStr )
85
+ if err != nil {
86
+ return nil , err
87
+ }
88
+ }
89
+
90
+ // If type is "commit, the tag is a lightweight tag
53
91
if ObjectType (tp ) == ObjectCommit {
92
+ commit , err := repo .GetCommit (id .String ())
93
+ if err != nil {
94
+ return nil , err
95
+ }
54
96
tag := & Tag {
55
- ID : id ,
56
- Object : id ,
57
- Type : string (ObjectCommit ),
58
- repo : repo ,
97
+ Name : name ,
98
+ ID : tagID ,
99
+ Object : commitID ,
100
+ Type : string (ObjectCommit ),
101
+ Tagger : commit .Committer ,
102
+ Message : commit .Message (),
103
+ repo : repo ,
59
104
}
60
105
61
106
repo .tagCache .Set (id .String (), tag )
62
107
return tag , nil
63
108
}
64
109
65
- // Tag with message.
110
+ // The tag is an annotated tag with a message.
66
111
data , err := NewCommand ("cat-file" , "-p" , id .String ()).RunInDirBytes (repo .Path )
67
112
if err != nil {
68
113
return nil , err
@@ -73,16 +118,57 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
73
118
return nil , err
74
119
}
75
120
121
+ tag .Name = name
76
122
tag .ID = id
77
123
tag .repo = repo
124
+ tag .Type = tp
78
125
79
126
repo .tagCache .Set (id .String (), tag )
80
127
return tag , nil
81
128
}
82
129
130
+ // GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
131
+ func (repo * Repository ) GetTagNameBySHA (sha string ) (string , error ) {
132
+ if len (sha ) < 5 {
133
+ return "" , fmt .Errorf ("SHA is too short: %s" , sha )
134
+ }
135
+
136
+ stdout , err := NewCommand ("show-ref" , "--tags" , "-d" ).RunInDir (repo .Path )
137
+ if err != nil {
138
+ return "" , err
139
+ }
140
+
141
+ tagRefs := strings .Split (stdout , "\n " )
142
+ for _ , tagRef := range tagRefs {
143
+ if len (strings .TrimSpace (tagRef )) > 0 {
144
+ fields := strings .Fields (tagRef )
145
+ if strings .HasPrefix (fields [0 ], sha ) && strings .HasPrefix (fields [1 ], TagPrefix ) {
146
+ name := fields [1 ][len (TagPrefix ):]
147
+ // annotated tags show up twice, their name for commit ID is suffixed with ^{}
148
+ name = strings .TrimSuffix (name , "^{}" )
149
+ return name , nil
150
+ }
151
+ }
152
+ }
153
+ return "" , ErrNotExist {ID : sha }
154
+ }
155
+
156
+ // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
157
+ func (repo * Repository ) GetTagID (name string ) (string , error ) {
158
+ stdout , err := NewCommand ("show-ref" , name ).RunInDir (repo .Path )
159
+ if err != nil {
160
+ return "" , err
161
+ }
162
+ fields := strings .Fields (stdout )
163
+ if len (fields ) != 2 {
164
+ return "" , ErrNotExist {ID : name }
165
+ }
166
+ return fields [0 ], nil
167
+ }
168
+
83
169
// GetTag returns a Git tag by given name.
84
170
func (repo * Repository ) GetTag (name string ) (* Tag , error ) {
85
- idStr , err := repo .GetTagCommitID (name )
171
+ idStr , err := repo .GetTagID (name )
86
172
if err != nil {
87
173
return nil , err
88
174
}
@@ -96,7 +182,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
96
182
if err != nil {
97
183
return nil , err
98
184
}
99
- tag .Name = name
100
185
return tag , nil
101
186
}
102
187
@@ -108,7 +193,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
108
193
return nil , err
109
194
}
110
195
111
- tagNames := strings .Split (stdout , "\n " )
196
+ tagNames := strings .Split (strings . TrimRight ( stdout , " \n " ) , "\n " )
112
197
var tags = make ([]* Tag , 0 , len (tagNames ))
113
198
for _ , tagName := range tagNames {
114
199
tagName = strings .TrimSpace (tagName )
@@ -120,6 +205,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
120
205
if err != nil {
121
206
return nil , err
122
207
}
208
+ tag .Name = tagName
123
209
tags = append (tags , tag )
124
210
}
125
211
sortTagsByTime (tags )
@@ -150,3 +236,38 @@ func (repo *Repository) GetTags() ([]string, error) {
150
236
151
237
return tagNames , nil
152
238
}
239
+
240
+ // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
241
+ func (repo * Repository ) GetTagType (id SHA1 ) (string , error ) {
242
+ // Get tag type
243
+ stdout , err := NewCommand ("cat-file" , "-t" , id .String ()).RunInDir (repo .Path )
244
+ if err != nil {
245
+ return "" , err
246
+ }
247
+ if len (stdout ) == 0 {
248
+ return "" , ErrNotExist {ID : id .String ()}
249
+ }
250
+ return strings .TrimSpace (stdout ), nil
251
+ }
252
+
253
+ // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
254
+ func (repo * Repository ) GetAnnotatedTag (sha string ) (* Tag , error ) {
255
+ id , err := NewIDFromString (sha )
256
+ if err != nil {
257
+ return nil , err
258
+ }
259
+
260
+ // Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
261
+ if tagType , err := repo .GetTagType (id ); err != nil {
262
+ return nil , err
263
+ } else if ObjectType (tagType ) != ObjectTag {
264
+ // not an annotated tag
265
+ return nil , ErrNotExist {ID : id .String ()}
266
+ }
267
+
268
+ tag , err := repo .getTag (id )
269
+ if err != nil {
270
+ return nil , err
271
+ }
272
+ return tag , nil
273
+ }
0 commit comments