@@ -22,6 +22,7 @@ import (
2222
2323 "gopkg.in/src-d/go-billy.v4"
2424 "gopkg.in/src-d/go-billy.v4/osfs"
25+ "bytes"
2526)
2627
2728var (
3839 ErrIsBareRepository = errors .New ("worktree not available in a bare repository" )
3940 ErrUnableToResolveCommit = errors .New ("unable to resolve commit" )
4041 ErrPackedObjectsNotSupported = errors .New ("Packed objects not supported" )
42+ ErrTagNotFound = errors .New ("tag not found" )
4143)
4244
4345// Repository represents a git repository
@@ -1220,3 +1222,150 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
12201222
12211223 return h , err
12221224}
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