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

Commit 9425c85

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 <eduardo.lezcano@be.atlascopco.com>
1 parent b30763c commit 9425c85

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

options.go

+38
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,41 @@ 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 {
465+
o.Abbrev = 7
466+
}
467+
if o.Candidates == 0 {
468+
o.Candidates = 10
469+
}
470+
return nil
471+
}

repository.go

+161
Original file line numberDiff line numberDiff line change
@@ -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
)
@@ -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,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+
}

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)