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

Commit 6104842

Browse files
author
Eduardo Lezcano
committed
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 <[email protected]>
1 parent b30763c commit 6104842

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
var (
@@ -38,6 +39,7 @@ var (
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+
}

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)