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