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

git describe #816

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,41 @@ type PlainOpenOptions struct {

// Validate validates the fields and sets the default values.
func (o *PlainOpenOptions) Validate() error { return nil }

// DescribeOptions as defined by `git describe`
type DescribeOptions struct {
// Contains find the tag that comes after the commit
//Contains bool
// Debug search strategy on stderr
Debug bool
// All Use any reference
//All bool
// Tags use any tag, even unannotated
Tags bool
// FirstParent only follow first parent
//FirstParent bool
// Use <Abbrev> digits to display SHA-1s
// By default is 8
Abbrev int
// Only output exact matches
//ExactMatch bool
// Consider <Candidates> most recent tags
// By default is 10
Candidates int
// Only consider tags matching <Match> pattern
//Match string
// Show abbreviated commit object as fallback
//Always bool
// Append <mark> on dirty working tree (default: "-dirty")
Dirty string
}

func (o *DescribeOptions) Validate() error {
if o.Abbrev == 0 {
o.Abbrev = 7
}
if o.Candidates == 0 {
o.Candidates = 10
}
return nil
}
138 changes: 138 additions & 0 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"gopkg.in/src-d/go-git.v4/storage/filesystem"
"gopkg.in/src-d/go-git.v4/utils/ioutil"

"bytes"
"gopkg.in/src-d/go-billy.v4"
"gopkg.in/src-d/go-billy.v4/osfs"
)
Expand All @@ -38,6 +39,7 @@ var (
ErrIsBareRepository = errors.New("worktree not available in a bare repository")
ErrUnableToResolveCommit = errors.New("unable to resolve commit")
ErrPackedObjectsNotSupported = errors.New("Packed objects not supported")
ErrTagNotFound = errors.New("tag not found")
)

// Repository represents a git repository
Expand Down Expand Up @@ -1220,3 +1222,139 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er

return h, err
}

type Describe struct {
// Reference being described
Reference *plumbing.Reference
// Tag of the describe object
Tag *plumbing.Reference
// Distance to the tag object in commits
Distance int
// Dirty string to append
Dirty string
// Use <Abbrev> digits to display SHA-ls
Abbrev int
}

func (d *Describe) String() string {
var s []string

if d.Tag != nil {
s = append(s, d.Tag.Name().Short())
}
if d.Distance > 0 {
s = append(s, fmt.Sprint(d.Distance))
}
s = append(s, "g"+d.Reference.Hash().String()[0:d.Abbrev])
if d.Dirty != "" {
s = append(s, d.Dirty)
}

return strings.Join(s, "-")
}

// Describe just like the `git describe` command will return a Describe struct for the hash passed.
// Describe struct implements String interface so it can be easily printed out.
func (r *Repository) Describe(ref *plumbing.Reference, opts *DescribeOptions) (*Describe, error) {
if err := opts.Validate(); err != nil {
return nil, err
}

// Describes through the commit log ordered by commit time seems to be the best approximation to
// git describe.
commitIterator, err := r.Log(&LogOptions{
From: ref.Hash(),
Order: LogOrderCommitterTime,
})
if err != nil {
return nil, err
}

// To query tags we create a temporary map.
tagIterator, err := r.Tags()
if err != nil {
return nil, err
}
tags := make(map[plumbing.Hash]*plumbing.Reference)
tagIterator.ForEach(func(t *plumbing.Reference) error {
if to, err := r.TagObject(t.Hash()); err == nil {
tags[to.Target] = t
} else {
tags[t.Hash()] = t
}
return nil
})
tagIterator.Close()

// The search looks for a number of suitable candidates in the log (specified through the options)
type describeCandidate struct {
ref *plumbing.Reference
annotated bool
distance int
}
var candidates []*describeCandidate
var candidatesFound int
var count = -1
var lastCommit *object.Commit

if opts.Debug {
fmt.Fprintf(os.Stderr, "searching to describe %v\n", ref.Name())
}

for {
var candidate = &describeCandidate{annotated: false}

err = commitIterator.ForEach(func(commit *object.Commit) error {
lastCommit = commit
count++
if tagReference, ok := tags[commit.Hash]; ok {
delete(tags, commit.Hash)
candidate.ref = tagReference
hash := tagReference.Hash()
if !bytes.Equal(commit.Hash[:], hash[:]) {
candidate.annotated = true
}
return storer.ErrStop
}
return nil
})

if candidate.annotated || opts.Tags {
if candidatesFound < opts.Candidates {
candidate.distance = count
candidates = append(candidates, candidate)
}
candidatesFound++
}

if candidatesFound > opts.Candidates || len(tags) == 0 {
break
}

}

if opts.Debug {
for _, c := range candidates {
var description = "lightweight"
if c.annotated {
description = "annotated"
}
fmt.Fprintf(os.Stderr, " %-11s %8d %v\n", description, c.distance, c.ref.Name().Short())
}
fmt.Fprintf(os.Stderr, "traversed %v commits\n", count)
if candidatesFound > opts.Candidates {
fmt.Fprintf(os.Stderr, "more than %v tags found; listed %v most recent\n",
opts.Candidates, len(candidates))
}
fmt.Fprintf(os.Stderr, "gave up search at %v\n", lastCommit.Hash.String())
}

return &Describe{
ref,
candidates[0].ref,
candidates[0].distance,
opts.Dirty,
opts.Abbrev,
}, nil

}
25 changes: 25 additions & 0 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1688,3 +1688,28 @@ func (s *RepositorySuite) TestBrokenMultipleShallowFetch(c *C) {
})
c.Assert(err, IsNil)
}

func (s *RepositorySuite) TestDescribe(c *C) {
url := s.GetLocalRepositoryURL(
fixtures.ByURL("https://github.com/git-fixtures/tags.git").One(),
)

r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{URL: url, Tags: AllTags})
c.Assert(err, IsNil)

datas := map[string]string{
"lightweight-tag-g7b8777": "f7b877701fbf855b44c0a9e86f3fdce2c298b07f",
}

for desc, hash := range datas {

h := plumbing.NewHash(hash)
d, err := r.Describe(
plumbing.NewHashReference("test", h),
&DescribeOptions{})

c.Assert(err, IsNil)
c.Assert(d.String(), Equals, desc)
}
}