@@ -20,6 +20,7 @@ import (
20
20
"gopkg.in/src-d/go-git.v4/storage/filesystem"
21
21
"gopkg.in/src-d/go-git.v4/utils/ioutil"
22
22
23
+ "bytes"
23
24
"gopkg.in/src-d/go-billy.v4"
24
25
"gopkg.in/src-d/go-billy.v4/osfs"
25
26
)
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,162 @@ 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 candidatesFound int
1297
+ var count = - 1
1298
+ var lastCommit * object.Commit
1299
+
1300
+ if opts .Debug {
1301
+ fmt .Fprintf (os .Stderr , "searching to describe %v\n " , ref .Name ())
1302
+ }
1303
+
1304
+ for {
1305
+ var candidate = & describeCandidate {annotated : false }
1306
+
1307
+ err = commitIterator .ForEach (func (commit * object.Commit ) error {
1308
+ lastCommit = commit
1309
+ count ++
1310
+ if tagReference , ok := tags [commit .Hash ]; ok {
1311
+ delete (tags , commit .Hash )
1312
+ candidate .ref = tagReference
1313
+ hash := tagReference .Hash ()
1314
+ if ! bytes .Equal (commit .Hash [:], hash [:]) {
1315
+ candidate .annotated = true
1316
+ }
1317
+ return storer .ErrStop
1318
+ }
1319
+ return nil
1320
+ })
1321
+
1322
+ if candidate .annotated || opts .Tags {
1323
+ if candidatesFound < opts .Candidates {
1324
+ candidate .distance = count
1325
+ candidates = append (candidates , candidate )
1326
+ }
1327
+ candidatesFound ++
1328
+ }
1329
+
1330
+ if candidatesFound > opts .Candidates || len (tags ) == 0 {
1331
+ break
1332
+ }
1333
+
1334
+ }
1335
+
1336
+ if opts .Debug {
1337
+ for _ , c := range candidates {
1338
+ var description = "lightweight"
1339
+ if c .annotated {
1340
+ description = "annotated"
1341
+ }
1342
+ fmt .Printf (" %-11s %8d %v\n " , description , c .distance , c .ref .Name ().Short ())
1343
+ }
1344
+ fmt .Fprintf (os .Stderr , "traversed %v commits\n " , count )
1345
+ if candidatesFound > opts .Candidates {
1346
+ fmt .Fprintf (os .Stderr , "more than %v tags found; listed %v most recent\n " ,
1347
+ opts .Candidates , len (candidates ))
1348
+ }
1349
+ fmt .Fprintf (os .Stderr , "gave up search at %v\n " , lastCommit .Hash .String ())
1350
+ }
1351
+
1352
+ return & Describe {
1353
+ ref ,
1354
+ candidates [0 ].ref ,
1355
+ candidates [0 ].distance ,
1356
+ opts .Dirty ,
1357
+ opts .Abbrev ,
1358
+ }, nil
1359
+
1360
+ }
1361
+
1362
+ func (r * Repository ) Tag (h plumbing.Hash ) (* plumbing.Reference , error ) {
1363
+ // Get repo tags
1364
+ tagIterator , err := r .Tags ()
1365
+ if err != nil {
1366
+ return nil , err
1367
+ }
1368
+ // Search tag
1369
+ var tag * plumbing.Reference = nil
1370
+ tagIterator .ForEach (func (t * plumbing.Reference ) error {
1371
+ tagHash := t .Hash ()
1372
+ if bytes .Equal (h [:], tagHash [:]) {
1373
+ tag = t
1374
+ return storer .ErrStop
1375
+ }
1376
+ return nil
1377
+ })
1378
+ // Closure
1379
+ if tag == nil {
1380
+ return nil , ErrTagNotFound
1381
+ }
1382
+ return tag , nil
1383
+ }
0 commit comments