@@ -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
2728// GitDirName this is a special folder where all the git stuff is.
4142 ErrIsBareRepository = errors .New ("worktree not available in a bare repository" )
4243 ErrUnableToResolveCommit = errors .New ("unable to resolve commit" )
4344 ErrPackedObjectsNotSupported = errors .New ("Packed objects not supported" )
45+ ErrTagNotFound = errors .New ("tag not found" )
4446)
4547
4648// Repository represents a git repository
@@ -1223,3 +1225,150 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
12231225
12241226 return h , err
12251227}
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