@@ -20,6 +20,7 @@ import (
2020 "gopkg.in/src-d/go-git.v4/storage/filesystem"
2121 "gopkg.in/src-d/go-git.v4/utils/ioutil"
2222
23+ "bytes"
2324 "gopkg.in/src-d/go-billy.v4"
2425 "gopkg.in/src-d/go-billy.v4/osfs"
2526)
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,162 @@ 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 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