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

Commit 9c319b2

Browse files
committed
Merge pull request #32 from scjalliance/annotated-tags
Annotated tags
2 parents ffe26fe + 916755f commit 9c319b2

File tree

8 files changed

+425
-4
lines changed

8 files changed

+425
-4
lines changed

commit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type Commit struct {
2929
}
3030

3131
func (c *Commit) Tree() *Tree {
32-
tree, _ := c.r.Tree(c.tree)
32+
tree, _ := c.r.Tree(c.tree) // FIXME: Return error as well?
3333
return tree
3434
}
3535

common_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"gopkg.in/src-d/go-git.v3/clients/common"
99
"gopkg.in/src-d/go-git.v3/core"
10+
"gopkg.in/src-d/go-git.v3/formats/packfile"
1011

1112
. "gopkg.in/check.v1"
1213
)
@@ -46,15 +47,41 @@ func (s *MockGitUploadPackService) Fetch(*common.GitUploadPackRequest) (io.ReadC
4647
return s.RC, err
4748
}
4849

49-
var fixtureRepos = [...]struct {
50+
type packedFixture struct {
5051
url string
5152
packfile string
52-
}{
53+
}
54+
55+
var fixtureRepos = []packedFixture{
5356
{"https://github.com/tyba/git-fixture.git", "formats/packfile/fixtures/git-fixture.ofs-delta"},
5457
{"https://github.com/jamesob/desk.git", "formats/packfile/fixtures/jamesob-desk.pack"},
5558
{"https://github.com/spinnaker/spinnaker.git", "formats/packfile/fixtures/spinnaker-spinnaker.pack"},
5659
}
5760

61+
func unpackFixtures(c *C, fixtures ...[]packedFixture) map[string]*Repository {
62+
repos := make(map[string]*Repository, 0)
63+
for _, group := range fixtures {
64+
for _, fixture := range group {
65+
if _, existing := repos[fixture.url]; existing {
66+
continue
67+
}
68+
repos[fixture.url] = NewPlainRepository()
69+
70+
d, err := os.Open(fixture.packfile)
71+
c.Assert(err, IsNil)
72+
73+
r := packfile.NewReader(d)
74+
r.Format = packfile.OFSDeltaFormat // This is hardcoded because we don't have a good way to sniff the format
75+
76+
_, err = r.Read(repos[fixture.url].Storage)
77+
c.Assert(err, IsNil)
78+
79+
c.Assert(d.Close(), IsNil)
80+
}
81+
}
82+
return repos
83+
}
84+
5885
type SuiteCommon struct{}
5986

6087
var _ = Suite(&SuiteCommon{})

core/object.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ func (t ObjectType) Bytes() []byte {
6464
return []byte(t.String())
6565
}
6666

67+
// ParseObjectType parses a string representation of ObjectType. It returns an
68+
// error on parse failure.
69+
func ParseObjectType(value string) (typ ObjectType, err error) {
70+
switch value {
71+
case "commit":
72+
typ = CommitObject
73+
case "tree":
74+
typ = TreeObject
75+
case "blob":
76+
typ = BlobObject
77+
case "tag":
78+
typ = TagObject
79+
case "ofs-delta":
80+
typ = OFSDeltaObject
81+
case "ref-delta":
82+
typ = REFDeltaObject
83+
default:
84+
err = errors.New("unable to parse object type")
85+
}
86+
return
87+
}
88+
6789
// ObjectIter is a generic closable interface for iterating over objects.
6890
type ObjectIter interface {
6991
Next() (Object, error)

repository.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,37 @@ func (r *Repository) Tree(h core.Hash) (*Tree, error) {
123123
tree := &Tree{r: r}
124124
return tree, tree.Decode(obj)
125125
}
126+
127+
// Blob returns the blob with the given hash
128+
func (r *Repository) Blob(h core.Hash) (*Blob, error) {
129+
obj, err := r.Storage.Get(h)
130+
if err != nil {
131+
if err == core.ObjectNotFoundErr {
132+
return nil, ObjectNotFoundErr
133+
}
134+
return nil, err
135+
}
136+
137+
blob := &Blob{}
138+
return blob, blob.Decode(obj)
139+
}
140+
141+
// Tag returns a tag with the given hash.
142+
func (r *Repository) Tag(h core.Hash) (*Tag, error) {
143+
obj, err := r.Storage.Get(h)
144+
if err != nil {
145+
if err == core.ObjectNotFoundErr {
146+
return nil, ObjectNotFoundErr
147+
}
148+
return nil, err
149+
}
150+
151+
tag := &Tag{r: r}
152+
return tag, tag.Decode(obj)
153+
}
154+
155+
// Tags returns a TagIter that can step through all of the annotated tags
156+
// in the repository.
157+
func (r *Repository) Tags() *TagIter {
158+
return NewTagIter(r, r.Storage.Iter(core.TagObject))
159+
}

repository_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package git
22

33
import (
4+
"fmt"
5+
46
"gopkg.in/src-d/go-git.v3/clients/http"
57
"gopkg.in/src-d/go-git.v3/core"
68

79
. "gopkg.in/check.v1"
810
)
911

10-
type SuiteRepository struct{}
12+
type SuiteRepository struct {
13+
repos map[string]*Repository
14+
}
1115

1216
var _ = Suite(&SuiteRepository{})
1317

18+
func (s *SuiteRepository) SetUpTest(c *C) {
19+
s.repos = unpackFixtures(c, tagFixtures)
20+
}
21+
1422
func (s *SuiteRepository) TestNewRepository(c *C) {
1523
r, err := NewRepository(RepositoryFixture, nil)
1624
c.Assert(err, IsNil)
@@ -77,6 +85,28 @@ func (s *SuiteRepository) TestCommits(c *C) {
7785
c.Assert(count, Equals, 8)
7886
}
7987

88+
func (s *SuiteRepository) TestTag(c *C) {
89+
for i, t := range tagTests {
90+
r, ok := s.repos[t.repo]
91+
c.Assert(ok, Equals, true)
92+
k := 0
93+
for hash, expected := range t.tags {
94+
tag, err := r.Tag(core.NewHash(hash))
95+
c.Assert(err, IsNil)
96+
testTagExpected(c, tag, expected, fmt.Sprintf("subtest %d, tag %d: ", i, k))
97+
k++
98+
}
99+
}
100+
}
101+
102+
func (s *SuiteRepository) TestTags(c *C) {
103+
for i, t := range tagTests {
104+
r, ok := s.repos[t.repo]
105+
c.Assert(ok, Equals, true)
106+
testTagIter(c, r.Tags(), t.tags, fmt.Sprintf("subtest %d, ", i))
107+
}
108+
}
109+
80110
func (s *SuiteRepository) TestCommitIterClosePanic(c *C) {
81111
r, err := NewRepository(RepositoryFixture, nil)
82112
r.Remotes["origin"].upSrv = &MockGitUploadPackService{}

storage/memory/storage.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type ObjectStorage struct {
1414
Commits map[core.Hash]core.Object
1515
Trees map[core.Hash]core.Object
1616
Blobs map[core.Hash]core.Object
17+
Tags map[core.Hash]core.Object
1718
}
1819

1920
// NewObjectStorage returns a new empty ObjectStorage
@@ -23,6 +24,7 @@ func NewObjectStorage() *ObjectStorage {
2324
Commits: make(map[core.Hash]core.Object, 0),
2425
Trees: make(map[core.Hash]core.Object, 0),
2526
Blobs: make(map[core.Hash]core.Object, 0),
27+
Tags: make(map[core.Hash]core.Object, 0),
2628
}
2729
}
2830

@@ -43,6 +45,8 @@ func (o *ObjectStorage) Set(obj core.Object) (core.Hash, error) {
4345
o.Trees[h] = o.Objects[h]
4446
case core.BlobObject:
4547
o.Blobs[h] = o.Objects[h]
48+
case core.TagObject:
49+
o.Tags[h] = o.Objects[h]
4650
default:
4751
return h, ErrUnsupportedObjectType
4852
}
@@ -70,6 +74,8 @@ func (o *ObjectStorage) Iter(t core.ObjectType) core.ObjectIter {
7074
series = flattenObjectMap(o.Trees)
7175
case core.BlobObject:
7276
series = flattenObjectMap(o.Blobs)
77+
case core.TagObject:
78+
series = flattenObjectMap(o.Tags)
7379
}
7480
return core.NewObjectSliceIter(series)
7581
}

tag.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package git
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
10+
"gopkg.in/src-d/go-git.v3/core"
11+
)
12+
13+
// Tag represents an annotated tag object. It points to a single git object of
14+
// any type, but tags typically are applied to commit or blob objects. It
15+
// provides a reference that associates the target with a tag name. It also
16+
// contains meta-information about the tag, including the tagger, tag date and
17+
// message.
18+
//
19+
// https://git-scm.com/book/en/v2/Git-Internals-Git-References#Tags
20+
type Tag struct {
21+
Hash core.Hash
22+
Type core.ObjectType
23+
Name string
24+
Tagger Signature
25+
Message string
26+
27+
object core.Hash
28+
r *Repository
29+
}
30+
31+
// Decode transforms a core.Object into a Tag struct.
32+
func (t *Tag) Decode(o core.Object) error {
33+
if o.Type() != core.TagObject {
34+
return ErrUnsupportedObject
35+
}
36+
37+
t.Hash = o.Hash()
38+
39+
r := bufio.NewReader(o.Reader())
40+
for {
41+
line, err := r.ReadSlice('\n')
42+
if err != nil && err != io.EOF {
43+
return err
44+
}
45+
46+
line = bytes.TrimSpace(line)
47+
if len(line) == 0 {
48+
break // Start of message
49+
}
50+
51+
split := bytes.SplitN(line, []byte{' '}, 2)
52+
switch string(split[0]) {
53+
case "object":
54+
t.object = core.NewHash(string(split[1]))
55+
case "type":
56+
t.Type, err = core.ParseObjectType(string(split[1]))
57+
if err != nil {
58+
return err
59+
}
60+
case "tag":
61+
t.Name = string(split[1])
62+
case "tagger":
63+
t.Tagger.Decode(split[1])
64+
}
65+
66+
if err == io.EOF {
67+
return nil
68+
}
69+
}
70+
71+
data, err := ioutil.ReadAll(r)
72+
if err != nil {
73+
return err
74+
}
75+
t.Message = string(data)
76+
77+
return nil
78+
}
79+
80+
// Commit returns the commit pointed to by the tag. If the tag points to a
81+
// different type of object ErrUnsupportedObject will be returned.
82+
func (t *Tag) Commit() (*Commit, error) {
83+
if t.Type != core.CommitObject {
84+
return nil, ErrUnsupportedObject
85+
}
86+
return t.r.Commit(t.object)
87+
}
88+
89+
// Tree returns the tree pointed to by the tag. If the tag points to a commit
90+
// object the tree of that commit will be returned. If the tag does not point
91+
// to a commit or tree object ErrUnsupportedObject will be returned.
92+
func (t *Tag) Tree() (*Tree, error) {
93+
// TODO: If the tag is of type commit, follow the commit to its tree?
94+
switch t.Type {
95+
case core.CommitObject:
96+
commit, err := t.r.Commit(t.object)
97+
if err != nil {
98+
return nil, err
99+
}
100+
return commit.Tree(), nil
101+
case core.TreeObject:
102+
return t.r.Tree(t.object)
103+
default:
104+
return nil, ErrUnsupportedObject
105+
}
106+
}
107+
108+
// Blob returns the blob pointed to by the tag. If the tag points to a
109+
// different type of object ErrUnsupportedObject will be returned.
110+
func (t *Tag) Blob() (*Blob, error) {
111+
if t.Type != core.BlobObject {
112+
return nil, ErrUnsupportedObject
113+
}
114+
return t.r.Blob(t.object)
115+
}
116+
117+
// Object returns the object pointed to by the tag.
118+
func (t *Tag) Object() (core.Object, error) {
119+
return t.r.Storage.Get(t.object)
120+
}
121+
122+
// String returns the meta information contained in the tag as a formatted
123+
// string.
124+
func (t *Tag) String() string {
125+
return fmt.Sprintf(
126+
"%s %s\nObject: %s\nType: %s\nTag: %s\nTagger: %s\nDate: %s\n",
127+
core.TagObject, t.Hash, t.object, t.Type, t.Name, t.Tagger.String(), t.Tagger.When,
128+
)
129+
}
130+
131+
// TagIter provides an iterator for a set of tags.
132+
type TagIter struct {
133+
core.ObjectIter
134+
r *Repository
135+
}
136+
137+
// NewTagIter returns a new TagIter for the given Repository and ObjectIter.
138+
func NewTagIter(r *Repository, iter core.ObjectIter) *TagIter {
139+
return &TagIter{iter, r}
140+
}
141+
142+
// Next moves the iterator to the next tag and returns a pointer to it. If it
143+
// has reached the end of the set it will return io.EOF.
144+
func (iter *TagIter) Next() (*Tag, error) {
145+
obj, err := iter.ObjectIter.Next()
146+
if err != nil {
147+
return nil, err
148+
}
149+
150+
tag := &Tag{r: iter.r}
151+
return tag, tag.Decode(obj)
152+
}
153+
154+
// Close releases any resources used by the iterator.
155+
func (iter *TagIter) Close() {
156+
iter.Close()
157+
}

0 commit comments

Comments
 (0)