Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit bcbda0d

Browse files
author
Eduardo Lezcano
committedApr 27, 2018
git describe functionality.
- `Describe` method under repository allows to describe references based on tags. - Options ported from `git describe` as close as possible. - Basic test for `Describe` Signed-off-by: Eduardo Lezcano <eduardo.lezcano@be.atlascopco.com>
1 parent 851f0f0 commit bcbda0d

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed
 

‎options.go

+33
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,36 @@ type PlainOpenOptions struct {
431431

432432
// Validate validates the fields and sets the default values.
433433
func (o *PlainOpenOptions) Validate() error { return nil }
434+
435+
// DescribeOptions as defined by `git describe`
436+
type DescribeOptions struct {
437+
// Contains find the tag that comes after the commit
438+
//Contains bool
439+
// Debug search strategy on stderr
440+
Debug bool
441+
// All Use any reference
442+
//All bool
443+
// Tags use any tag, even unannotated
444+
Tags bool
445+
// FirstParent only follow first parent
446+
//FirstParent bool
447+
// Use <Abbrev> digits to display SHA-1s
448+
// By default is 8
449+
Abbrev int
450+
// Only output exact matches
451+
//ExactMatch bool
452+
// Consider <Candidates> most recent tags
453+
// By default is 10
454+
Candidates int
455+
// Only consider tags matching <Match> pattern
456+
//Match string
457+
// Show abbreviated commit object as fallback
458+
//Always bool
459+
// Append <mark> on dirty working tree (default: "-dirty")
460+
Dirty string
461+
}
462+
463+
func (o *DescribeOptions) Validate() error {
464+
if o.Abbrev == 0 { o.Abbrev = 7 }
465+
if o.Candidates == 0 { o.Candidates = 10}
466+
return nil }

‎repository.go

+149
Original file line numberDiff line numberDiff line change
@@ -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.
@@ -41,6 +42,7 @@ var (
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+
}

‎repository_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -1688,3 +1688,28 @@ func (s *RepositorySuite) TestBrokenMultipleShallowFetch(c *C) {
16881688
})
16891689
c.Assert(err, IsNil)
16901690
}
1691+
1692+
func (s *RepositorySuite) TestDescribe(c *C) {
1693+
url := s.GetLocalRepositoryURL(
1694+
fixtures.ByURL("https://github.com/git-fixtures/tags.git").One(),
1695+
)
1696+
1697+
r, _ := Init(memory.NewStorage(), nil)
1698+
err := r.clone(context.Background(), &CloneOptions{URL: url, Tags:AllTags})
1699+
c.Assert(err, IsNil)
1700+
1701+
datas := map[string]string{
1702+
"lightweight-tag-g7b8777": "f7b877701fbf855b44c0a9e86f3fdce2c298b07f",
1703+
}
1704+
1705+
for desc, hash := range datas {
1706+
1707+
h := plumbing.NewHash(hash)
1708+
d, err := r.Describe(
1709+
plumbing.NewHashReference("test", h),
1710+
&DescribeOptions{})
1711+
1712+
c.Assert(err, IsNil)
1713+
c.Assert(d.String(), Equals, desc)
1714+
}
1715+
}

0 commit comments

Comments
 (0)
Failed to load comments.