@@ -22,6 +22,7 @@ import (
22
22
23
23
"gopkg.in/src-d/go-billy.v4"
24
24
"gopkg.in/src-d/go-billy.v4/osfs"
25
+ "bytes"
25
26
)
26
27
27
28
var (
38
39
ErrIsBareRepository = errors .New ("worktree not available in a bare repository" )
39
40
ErrUnableToResolveCommit = errors .New ("unable to resolve commit" )
40
41
ErrPackedObjectsNotSupported = errors .New ("Packed objects not supported" )
42
+ ErrTagNotFound = errors .New ("tag not found" )
41
43
)
42
44
43
45
// Repository represents a git repository
@@ -1220,3 +1222,150 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
1220
1222
1221
1223
return h , err
1222
1224
}
1225
+
1226
+ type Describe struct {
1227
+ // Reference being described
1228
+ Reference * plumbing.Reference
1229
+ // Tag of the describe object
1230
+ Tag * plumbing.Reference
1231
+ // Distance to the tag object in commits
1232
+ Distance int
1233
+ // Dirty string to append
1234
+ Dirty string
1235
+ // Use <Abbrev> digits to display SHA-ls
1236
+ Abbrev int
1237
+ }
1238
+
1239
+ func (d * Describe ) String () string {
1240
+ var s []string
1241
+
1242
+ if d .Tag != nil {
1243
+ s = append (s , d .Tag .Name ().Short ())
1244
+ }
1245
+ if d .Distance > 0 {
1246
+ s = append (s , fmt .Sprint (d .Distance ))
1247
+ }
1248
+ s = append (s , "g" + d .Reference .Hash ().String ()[0 :d .Abbrev ])
1249
+ if d .Dirty != "" {
1250
+ s = append (s , d .Dirty )
1251
+ }
1252
+
1253
+ return strings .Join (s , "-" )
1254
+ }
1255
+
1256
+ // Describe just like the `git describe` command will return a Describe struct for the hash passed.
1257
+ // Describe struct implements String interface so it can be easily printed out.
1258
+ func (r * Repository ) Describe (ref * plumbing.Reference , opts * DescribeOptions ) (* Describe , error ) {
1259
+ if err := opts .Validate (); err != nil {
1260
+ return nil , err
1261
+ }
1262
+
1263
+ // Describes through the commit log ordered by commit time seems to be the best approximation to
1264
+ // git describe.
1265
+ commitIterator , err := r .Log (& LogOptions {
1266
+ From : ref .Hash (),
1267
+ Order : LogOrderCommitterTime ,
1268
+ })
1269
+ if err != nil {
1270
+ return nil , err
1271
+ }
1272
+
1273
+ // To query tags we create a temporary map.
1274
+ tagIterator , err := r .Tags ()
1275
+ if err != nil {
1276
+ return nil , err
1277
+ }
1278
+ tags := make (map [plumbing.Hash ]* plumbing.Reference )
1279
+ tagIterator .ForEach (func (t * plumbing.Reference ) error {
1280
+ if to , err := r .TagObject (t .Hash ()); err == nil {
1281
+ tags [to .Target ] = t
1282
+ } else {
1283
+ tags [t .Hash ()] = t
1284
+ }
1285
+ return nil
1286
+ })
1287
+ tagIterator .Close ()
1288
+
1289
+ // The search looks for a number of suitable candidates in the log (specified through the options)
1290
+ type describeCandidate struct {
1291
+ ref * plumbing.Reference
1292
+ annotated bool
1293
+ distance int
1294
+ }
1295
+ var candidates []* describeCandidate
1296
+ var count = - 1
1297
+ var lastCommit * object.Commit
1298
+
1299
+ if (opts .Debug ) {
1300
+ fmt .Printf ("searching to describe %v\n " ,ref .Name ())
1301
+ }
1302
+
1303
+ for {
1304
+ var candidate = & describeCandidate {annotated : false }
1305
+
1306
+ err = commitIterator .ForEach (func (commit * object.Commit ) error {
1307
+ lastCommit = commit
1308
+ count ++
1309
+ if tagReference , ok := tags [commit .Hash ]; ok {
1310
+ delete (tags , commit .Hash )
1311
+ candidate .ref = tagReference
1312
+ hash := tagReference .Hash ()
1313
+ if ! bytes .Equal (commit .Hash [:],hash [:]) { candidate .annotated = true }
1314
+ return storer .ErrStop
1315
+ }
1316
+ return nil
1317
+ })
1318
+
1319
+ if candidate .annotated || opts .Tags {
1320
+ candidate .distance = count
1321
+ candidates = append (candidates , candidate )
1322
+ }
1323
+
1324
+ if len (candidates ) >= opts .Candidates || len (tags ) == 0 { break }
1325
+
1326
+ }
1327
+
1328
+ if (opts .Debug ) {
1329
+ for _ , c := range candidates {
1330
+ var description = "lightweight"
1331
+ if c .annotated { description = "annotated" }
1332
+ fmt .Printf (" %-11s %8d %v\n " , description , c .distance , c .ref .Name ().Short ())
1333
+ }
1334
+ fmt .Printf ("traversed %v commits\n " +
1335
+ "more than %v tags found; listed %v most recent\n " +
1336
+ "gave up search at %v\n " ,
1337
+ count , opts .Candidates , opts .Candidates , lastCommit .Hash .String ())
1338
+ }
1339
+
1340
+ return & Describe {
1341
+ ref ,
1342
+ candidates [0 ].ref ,
1343
+ candidates [0 ].distance ,
1344
+ opts .Dirty ,
1345
+ opts .Abbrev ,
1346
+ }, nil
1347
+
1348
+ }
1349
+
1350
+ func (r * Repository ) Tag (h plumbing.Hash ) (* plumbing.Reference , error ){
1351
+ // Get repo tags
1352
+ tagIterator , err := r .Tags ()
1353
+ if err != nil {
1354
+ return nil , err
1355
+ }
1356
+ // Search tag
1357
+ var tag * plumbing.Reference = nil
1358
+ tagIterator .ForEach (func (t * plumbing.Reference ) error {
1359
+ tagHash := t .Hash ()
1360
+ if bytes .Equal (h [:], tagHash [:]){
1361
+ tag = t
1362
+ return storer .ErrStop
1363
+ }
1364
+ return nil
1365
+ })
1366
+ // Closure
1367
+ if tag == nil {
1368
+ return nil , ErrTagNotFound
1369
+ }
1370
+ return tag , nil
1371
+ }
0 commit comments