aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commit.go127
-rw-r--r--common_test.go31
-rw-r--r--internal/hash.go5
-rw-r--r--internal/hash_test.go8
-rw-r--r--internal/object.go18
-rw-r--r--objects.go143
-rw-r--r--objects_test.go68
-rw-r--r--remote_test.go6
-rw-r--r--repository.go40
-rw-r--r--repository_test.go46
-rw-r--r--tree.go122
11 files changed, 447 insertions, 167 deletions
diff --git a/commit.go b/commit.go
new file mode 100644
index 0000000..b8a2772
--- /dev/null
+++ b/commit.go
@@ -0,0 +1,127 @@
+package git
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+
+ "gopkg.in/src-d/go-git.v2/internal"
+)
+
+// Commit points to a single tree, marking it as what the project looked like
+// at a certain point in time. It contains meta-information about that point
+// in time, such as a timestamp, the author of the changes since the last
+// commit, a pointer to the previous commit(s), etc.
+// http://schacon.github.io/gitbook/1_the_git_object_model.html
+type Commit struct {
+ Hash internal.Hash
+ Author Signature
+ Committer Signature
+ Message string
+
+ tree internal.Hash
+ parents []internal.Hash
+ r *Repository
+}
+
+func (c *Commit) Tree() *Tree {
+ tree, _ := c.r.Tree(c.tree)
+ return tree
+}
+
+func (c *Commit) Parents() *CommitIter {
+ i := NewCommitIter(c.r)
+ go func() {
+ defer i.Close()
+ for _, hash := range c.parents {
+ obj, _ := c.r.Storage.Get(hash)
+ i.Add(obj)
+ }
+ }()
+
+ return i
+}
+
+// Decode transform an internal.Object into a Blob struct
+func (c *Commit) Decode(o internal.Object) error {
+ c.Hash = o.Hash()
+ r := bufio.NewReader(o.Reader())
+
+ var message bool
+ for {
+ line, err := r.ReadSlice('\n')
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ line = bytes.TrimSpace(line)
+ if !message {
+ if len(line) == 0 {
+ message = true
+ continue
+ }
+
+ split := bytes.SplitN(line, []byte{' '}, 2)
+ switch string(split[0]) {
+ case "tree":
+ c.tree = internal.NewHash(string(split[1]))
+ case "parent":
+ c.parents = append(c.parents, internal.NewHash(string(split[1])))
+ case "author":
+ c.Author.Decode(split[1])
+ case "committer":
+ c.Committer.Decode(split[1])
+ }
+ } else {
+ c.Message += string(line) + "\n"
+ }
+
+ if err == io.EOF {
+ return nil
+ }
+ }
+}
+
+func (c *Commit) String() string {
+ return fmt.Sprintf(
+ "%s %s\nAuthor: %s\nDate: %s\n",
+ internal.CommitObject, c.Hash, c.Author.String(), c.Author.When,
+ )
+}
+
+type CommitIter struct {
+ iter
+}
+
+func NewCommitIter(r *Repository) *CommitIter {
+ return &CommitIter{newIter(r)}
+}
+
+func (i *CommitIter) Next() (*Commit, error) {
+ obj := <-i.ch
+ if obj == nil {
+ return nil, io.EOF
+ }
+
+ commit := &Commit{r: i.r}
+ return commit, commit.Decode(obj)
+}
+
+type iter struct {
+ ch chan internal.Object
+ r *Repository
+}
+
+func newIter(r *Repository) iter {
+ ch := make(chan internal.Object, 1)
+ return iter{ch, r}
+}
+
+func (i *iter) Add(o internal.Object) {
+ i.ch <- o
+}
+
+func (i *iter) Close() {
+ close(i.ch)
+}
diff --git a/common_test.go b/common_test.go
index 3f25ad9..446bc16 100644
--- a/common_test.go
+++ b/common_test.go
@@ -1,9 +1,40 @@
package git
import (
+ "io"
+ "net/url"
+ "os"
+ "strings"
"testing"
. "gopkg.in/check.v1"
+ "gopkg.in/src-d/go-git.v2/clients/common"
+ "gopkg.in/src-d/go-git.v2/internal"
)
func Test(t *testing.T) { TestingT(t) }
+
+type MockGitUploadPackService struct{}
+
+func (s *MockGitUploadPackService) Connect(url common.Endpoint) error {
+ return nil
+}
+
+func (s *MockGitUploadPackService) Info() (*common.GitUploadPackInfo, error) {
+ hash := internal.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+
+ line := "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEADmulti_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2:2.4.8~dbussink-fix-enterprise-tokens-compilation-1167-gc7006cf"
+ values, _ := url.ParseQuery(strings.Replace(line, " ", "&", -1))
+
+ return &common.GitUploadPackInfo{
+ Capabilities: common.Capabilities(values),
+ Refs: map[string]*common.RemoteHead{
+ "refs/heads/master": &common.RemoteHead{Id: hash},
+ },
+ }, nil
+}
+
+func (s *MockGitUploadPackService) Fetch(*common.GitUploadPackRequest) (io.ReadCloser, error) {
+ r, _ := os.Open("formats/packfile/fixtures/git-fixture.ref-delta")
+ return r, nil
+}
diff --git a/internal/hash.go b/internal/hash.go
index db55b24..0540db1 100644
--- a/internal/hash.go
+++ b/internal/hash.go
@@ -30,6 +30,11 @@ func NewHash(s string) Hash {
return h
}
+func (h Hash) IsZero() bool {
+ var empty Hash
+ return h == empty
+}
+
func (h Hash) String() string {
return hex.EncodeToString(h[:])
}
diff --git a/internal/hash_test.go b/internal/hash_test.go
index 222f3b4..063bd23 100644
--- a/internal/hash_test.go
+++ b/internal/hash_test.go
@@ -25,3 +25,11 @@ func (s *HashSuite) TestNewHash(c *C) {
c.Assert(hash, Equals, NewHash(hash.String()))
}
+
+func (s *HashSuite) TestIsZero(c *C) {
+ hash := NewHash("foo")
+ c.Assert(hash.IsZero(), Equals, true)
+
+ hash = NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d")
+ c.Assert(hash.IsZero(), Equals, false)
+}
diff --git a/internal/object.go b/internal/object.go
index 0f11140..a30bc9d 100644
--- a/internal/object.go
+++ b/internal/object.go
@@ -71,18 +71,18 @@ func (o *RAWObject) Write(p []byte) (n int, err error) {
}
type RAWObjectStorage struct {
- Objects map[Hash]*RAWObject
- Commits map[Hash]*RAWObject
- Trees map[Hash]*RAWObject
- Blobs map[Hash]*RAWObject
+ Objects map[Hash]Object
+ Commits map[Hash]Object
+ Trees map[Hash]Object
+ Blobs map[Hash]Object
}
func NewRAWObjectStorage() *RAWObjectStorage {
return &RAWObjectStorage{
- Objects: make(map[Hash]*RAWObject, 0),
- Commits: make(map[Hash]*RAWObject, 0),
- Trees: make(map[Hash]*RAWObject, 0),
- Blobs: make(map[Hash]*RAWObject, 0),
+ Objects: make(map[Hash]Object, 0),
+ Commits: make(map[Hash]Object, 0),
+ Trees: make(map[Hash]Object, 0),
+ Blobs: make(map[Hash]Object, 0),
}
}
@@ -92,7 +92,7 @@ func (o *RAWObjectStorage) New() Object {
func (o *RAWObjectStorage) Set(obj Object) Hash {
h := obj.Hash()
- o.Objects[h] = obj.(*RAWObject)
+ o.Objects[h] = obj
switch obj.Type() {
case CommitObject:
diff --git a/objects.go b/objects.go
index a82a73b..49c0e9e 100644
--- a/objects.go
+++ b/objects.go
@@ -1,134 +1,20 @@
package git
import (
- "bufio"
- "bytes"
"fmt"
"io"
- "os"
"strconv"
"time"
"gopkg.in/src-d/go-git.v2/internal"
)
-// Commit points to a single tree, marking it as what the project looked like
-// at a certain point in time. It contains meta-information about that point
-// in time, such as a timestamp, the author of the changes since the last
-// commit, a pointer to the previous commit(s), etc.
-// http://schacon.github.io/gitbook/1_the_git_object_model.html
-type Commit struct {
- Hash internal.Hash
- Tree internal.Hash
- Parents []internal.Hash
- Author Signature
- Committer Signature
- Message string
-}
-
-// Decode transform an internal.Object into a Blob struct
-func (c *Commit) Decode(o internal.Object) error {
- c.Hash = o.Hash()
- r := bufio.NewReader(o.Reader())
-
- var message bool
- for {
- line, err := r.ReadSlice('\n')
- if err != nil && err != io.EOF {
- return err
- }
-
- line = bytes.TrimSpace(line)
- if !message {
- if len(line) == 0 {
- message = true
- continue
- }
-
- split := bytes.SplitN(line, []byte{' '}, 2)
- switch string(split[0]) {
- case "tree":
- c.Tree = internal.NewHash(string(split[1]))
- case "parent":
- c.Parents = append(c.Parents, internal.NewHash(string(split[1])))
- case "author":
- c.Author = ParseSignature(split[1])
- case "committer":
- c.Committer = ParseSignature(split[1])
- }
- } else {
- c.Message += string(line) + "\n"
- }
-
- if err == io.EOF {
- return nil
- }
- }
-}
-
-// Tree is basically like a directory - it references a bunch of other trees
-// and/or blobs (i.e. files and sub-directories)
-type Tree struct {
- Entries []TreeEntry
- Hash internal.Hash
-}
-
-// TreeEntry represents a file
-type TreeEntry struct {
- Name string
- Mode os.FileMode
- Hash internal.Hash
-}
-
-// Decode transform an internal.Object into a Tree struct
-func (t *Tree) Decode(o internal.Object) error {
- t.Hash = o.Hash()
- if o.Size() == 0 {
- return nil
- }
-
- r := bufio.NewReader(o.Reader())
- for {
- mode, err := r.ReadString(' ')
- if err != nil {
- if err == io.EOF {
- break
- }
-
- return err
- }
-
- fm, err := strconv.ParseInt(mode[:len(mode)-1], 8, 32)
- if err != nil && err != io.EOF {
- return err
- }
-
- name, err := r.ReadString(0)
- if err != nil && err != io.EOF {
- return err
- }
-
- var hash internal.Hash
- _, err = r.Read(hash[:])
- if err != nil && err != io.EOF {
- return err
- }
-
- t.Entries = append(t.Entries, TreeEntry{
- Hash: hash,
- Mode: os.FileMode(fm),
- Name: name[:len(name)-1],
- })
- }
-
- return nil
-}
-
// Blob is used to store file data - it is generally a file.
type Blob struct {
Hash internal.Hash
Size int64
- obj internal.Object
+
+ obj internal.Object
}
// Decode transform an internal.Object into a Blob struct
@@ -152,11 +38,10 @@ type Signature struct {
When time.Time
}
-// ParseSignature parse a byte slice returning a new action signature.
-func ParseSignature(signature []byte) Signature {
- ret := Signature{}
- if len(signature) == 0 {
- return ret
+// Decode decodes a byte slice into a signature
+func (s *Signature) Decode(b []byte) {
+ if len(b) == 0 {
+ return
}
from := 0
@@ -164,8 +49,8 @@ func ParseSignature(signature []byte) Signature {
for i := 0; ; i++ {
var c byte
var end bool
- if i < len(signature) {
- c = signature[i]
+ if i < len(b) {
+ c = b[i]
} else {
end = true
}
@@ -176,22 +61,22 @@ func ParseSignature(signature []byte) Signature {
if i == 0 {
break
}
- ret.Name = string(signature[from : i-1])
+ s.Name = string(b[from : i-1])
state = 'e'
from = i + 1
}
case 'e':
if c == '>' || end {
- ret.Email = string(signature[from:i])
+ s.Email = string(b[from:i])
i++
state = 't'
from = i + 1
}
case 't':
if c == ' ' || end {
- t, err := strconv.ParseInt(string(signature[from:i]), 10, 64)
+ t, err := strconv.ParseInt(string(b[from:i]), 10, 64)
if err == nil {
- ret.When = time.Unix(t, 0)
+ s.When = time.Unix(t, 0)
}
end = true
}
@@ -201,10 +86,8 @@ func ParseSignature(signature []byte) Signature {
break
}
}
-
- return ret
}
func (s *Signature) String() string {
- return fmt.Sprintf("%q <%s> @ %s", s.Name, s.Email, s.When)
+ return fmt.Sprintf("%s <%s>", s.Name, s.Email)
}
diff --git a/objects_test.go b/objects_test.go
index ec39d44..8c2959f 100644
--- a/objects_test.go
+++ b/objects_test.go
@@ -1,7 +1,6 @@
package git
import (
- "encoding/base64"
"io/ioutil"
"time"
@@ -9,27 +8,38 @@ import (
"gopkg.in/src-d/go-git.v2/internal"
)
-type ObjectsSuite struct{}
+type ObjectsSuite struct {
+ r *Repository
+}
var _ = Suite(&ObjectsSuite{})
-var CommitFixture = "dHJlZSBjMmQzMGZhOGVmMjg4NjE4ZjY1ZjZlZWQ2ZTE2OGUwZDUxNDg4NmY0CnBhcmVudCBiMDI5NTE3ZjYzMDBjMmRhMGY0YjY1MWI4NjQyNTA2Y2Q2YWFmNDVkCnBhcmVudCBiOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3CmF1dGhvciBNw6F4aW1vIEN1YWRyb3MgPG1jdWFkcm9zQGdtYWlsLmNvbT4gMTQyNzgwMjQzNCArMDIwMApjb21taXR0ZXIgTcOheGltbyBDdWFkcm9zIDxtY3VhZHJvc0BnbWFpbC5jb20+IDE0Mjc4MDI0MzQgKzAyMDAKCk1lcmdlIHB1bGwgcmVxdWVzdCAjMSBmcm9tIGRyaXBvbGxlcy9mZWF0dXJlCgpDcmVhdGluZyBjaGFuZ2Vsb2c="
+func (s *ObjectsSuite) SetUpTest(c *C) {
+ var err error
+ s.r, err = NewRepository(RepositoryFixture)
+ s.r.Remotes["origin"].upSrv = &MockGitUploadPackService{}
+
+ s.r.Pull("origin", "refs/heads/master")
+ c.Assert(err, IsNil)
+}
func (s *ObjectsSuite) TestNewCommit(c *C) {
- data, _ := base64.StdEncoding.DecodeString(CommitFixture)
+ hash := internal.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69")
+ commit, err := s.r.Commit(hash)
+ c.Assert(err, IsNil)
- o := &internal.RAWObject{}
- o.SetType(internal.CommitObject)
- o.Writer().Write(data)
+ c.Assert(commit.Hash.String(), Equals, "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69")
+ c.Assert(commit.Tree().Hash.String(), Equals, "c2d30fa8ef288618f65f6eed6e168e0d514886f4")
- commit := &Commit{}
- c.Assert(commit.Decode(o), IsNil)
+ parents := commit.Parents()
+ parentCommit, err := parents.Next()
+ c.Assert(err, IsNil)
+ c.Assert(parentCommit.Hash.String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")
+
+ parentCommit, err = parents.Next()
+ c.Assert(err, IsNil)
+ c.Assert(parentCommit.Hash.String(), Equals, "b8e471f58bcbca63b07bda20e428190409c2db47")
- c.Assert(commit.Hash.String(), Equals, "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69")
- c.Assert(commit.Tree.String(), Equals, "c2d30fa8ef288618f65f6eed6e168e0d514886f4")
- c.Assert(commit.Parents, HasLen, 2)
- c.Assert(commit.Parents[0].String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")
- c.Assert(commit.Parents[1].String(), Equals, "b8e471f58bcbca63b07bda20e428190409c2db47")
c.Assert(commit.Author.Email, Equals, "mcuadros@gmail.com")
c.Assert(commit.Author.Name, Equals, "Máximo Cuadros")
c.Assert(commit.Author.When.Unix(), Equals, int64(1427802434))
@@ -37,23 +47,27 @@ func (s *ObjectsSuite) TestNewCommit(c *C) {
c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog\n")
}
-var TreeFixture = "MTAwNjQ0IC5naXRpZ25vcmUAMoWKrTw4PtH/Cg+b3yMdVKAMnogxMDA2NDQgQ0hBTkdFTE9HANP/U+BWSp+H2OhLbijlBg5RcAiqMTAwNjQ0IExJQ0VOU0UAwZK9aiTqGrAdeGhuQXyL3Hw9GX8xMDA2NDQgYmluYXJ5LmpwZwDVwPSrgRiXyt8DrsNYrmDSH5HFDTQwMDAwIGdvAKOXcadlH5f69ccuCCJNhX/DUTPbNDAwMDAganNvbgBah35qkGonQ61uRdmcF5NkKq+O2jQwMDAwIHBocABYavVn0Ltedx5JvdlDT14Pt20l+jQwMDAwIHZlbmRvcgDPSqOziXT7fYHzZ8CDD3141lq4aw=="
-
func (s *ObjectsSuite) TestParseTree(c *C) {
- data, _ := base64.StdEncoding.DecodeString(TreeFixture)
-
- o := &internal.RAWObject{}
- o.SetType(internal.TreeObject)
- o.SetSize(int64(len(data)))
- o.Writer().Write(data)
-
- tree := &Tree{}
- c.Assert(tree.Decode(o), IsNil)
+ hash := internal.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")
+ tree, err := s.r.Tree(hash)
+ c.Assert(err, IsNil)
c.Assert(tree.Entries, HasLen, 8)
c.Assert(tree.Entries[0].Name, Equals, ".gitignore")
c.Assert(tree.Entries[0].Mode.String(), Equals, "-rw-r--r--")
c.Assert(tree.Entries[0].Hash.String(), Equals, "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88")
+
+ count := 0
+ ch := tree.Files()
+ for f := range ch {
+ count++
+ if f.Name == "go/example.go" {
+ content, _ := ioutil.ReadAll(f)
+ c.Assert(content, HasLen, 2780)
+ }
+ }
+
+ c.Assert(count, Equals, 9)
}
func (s *ObjectsSuite) TestBlobHash(c *C) {
@@ -108,7 +122,9 @@ func (s *ObjectsSuite) TestParseSignature(c *C) {
}
for raw, exp := range cases {
- got := ParseSignature([]byte(raw))
+ got := &Signature{}
+ got.Decode([]byte(raw))
+
c.Assert(got.Name, Equals, exp.Name)
c.Assert(got.Email, Equals, exp.Email)
c.Assert(got.When.Unix(), Equals, exp.When.Unix())
diff --git a/remote_test.go b/remote_test.go
index 5cb2e30..6d8c2ff 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -21,6 +21,8 @@ func (s *SuiteRemote) TestConnect(c *C) {
func (s *SuiteRemote) TestDefaultBranch(c *C) {
r, err := NewRemote(RepositoryFixture)
+ r.upSrv = &MockGitUploadPackService{}
+
c.Assert(err, IsNil)
c.Assert(r.Connect(), IsNil)
c.Assert(r.DefaultBranch(), Equals, "refs/heads/master")
@@ -28,6 +30,8 @@ func (s *SuiteRemote) TestDefaultBranch(c *C) {
func (s *SuiteRemote) TestCapabilities(c *C) {
r, err := NewRemote(RepositoryFixture)
+ r.upSrv = &MockGitUploadPackService{}
+
c.Assert(err, IsNil)
c.Assert(r.Connect(), IsNil)
c.Assert(r.Capabilities().Get("agent"), HasLen, 1)
@@ -35,6 +39,8 @@ func (s *SuiteRemote) TestCapabilities(c *C) {
func (s *SuiteRemote) TestFetchDefaultBranch(c *C) {
r, err := NewRemote(RepositoryFixture)
+ r.upSrv = &MockGitUploadPackService{}
+
c.Assert(err, IsNil)
c.Assert(r.Connect(), IsNil)
diff --git a/repository.go b/repository.go
index 2dadcb6..c732d0f 100644
--- a/repository.go
+++ b/repository.go
@@ -1,6 +1,7 @@
package git
import (
+ "errors"
"fmt"
"gopkg.in/src-d/go-git.v2/clients/common"
@@ -8,6 +9,10 @@ import (
"gopkg.in/src-d/go-git.v2/internal"
)
+var (
+ ObjectNotFoundErr = errors.New("object not found")
+)
+
const (
DefaultRemoteName = "origin"
)
@@ -66,3 +71,38 @@ func (r *Repository) Pull(remoteName, branch string) error {
return nil
}
+
+// Commit return the commit with the given hash
+func (r *Repository) Commit(h internal.Hash) (*Commit, error) {
+ obj, ok := r.Storage.Get(h)
+ if !ok {
+ return nil, ObjectNotFoundErr
+ }
+
+ commit := &Commit{r: r}
+ return commit, commit.Decode(obj)
+}
+
+// Commits decode the objects into commits
+func (r *Repository) Commits() *CommitIter {
+ i := NewCommitIter(r)
+ go func() {
+ defer i.Close()
+ for _, obj := range r.Storage.Commits {
+ i.Add(obj)
+ }
+ }()
+
+ return i
+}
+
+// Tree return the tree with the given hash
+func (r *Repository) Tree(h internal.Hash) (*Tree, error) {
+ obj, ok := r.Storage.Get(h)
+ if !ok {
+ return nil, ObjectNotFoundErr
+ }
+
+ tree := &Tree{r: r}
+ return tree, tree.Decode(obj)
+}
diff --git a/repository_test.go b/repository_test.go
index d0701fa..d377478 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -1,6 +1,9 @@
package git
-import . "gopkg.in/check.v1"
+import (
+ . "gopkg.in/check.v1"
+ "gopkg.in/src-d/go-git.v2/internal"
+)
type SuiteRepository struct{}
@@ -8,8 +11,47 @@ var _ = Suite(&SuiteRepository{})
func (s *SuiteRepository) TestPull(c *C) {
r, err := NewRepository(RepositoryFixture)
+ r.Remotes["origin"].upSrv = &MockGitUploadPackService{}
+
+ c.Assert(err, IsNil)
+ c.Assert(r.Pull("origin", "refs/heads/master"), IsNil)
+}
+
+func (s *SuiteRepository) TestCommit(c *C) {
+ r, err := NewRepository(RepositoryFixture)
+ r.Remotes["origin"].upSrv = &MockGitUploadPackService{}
+
+ c.Assert(err, IsNil)
+ c.Assert(r.Pull("origin", "refs/heads/master"), IsNil)
+
+ hash := internal.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47")
+ commit, err := r.Commit(hash)
+ c.Assert(err, IsNil)
+
+ c.Assert(commit.Hash.IsZero(), Equals, false)
+ c.Assert(commit.Tree().Hash.IsZero(), Equals, false)
+ c.Assert(commit.Author.Email, Equals, "daniel@lordran.local")
+}
+
+func (s *SuiteRepository) TestCommits(c *C) {
+ r, err := NewRepository(RepositoryFixture)
+ r.Remotes["origin"].upSrv = &MockGitUploadPackService{}
+
c.Assert(err, IsNil)
c.Assert(r.Pull("origin", "refs/heads/master"), IsNil)
- //fmt.Println(r.Storage)
+ count := 0
+ commits := r.Commits()
+ for {
+ commit, err := commits.Next()
+ if err != nil {
+ break
+ }
+
+ count++
+ c.Assert(commit.Hash.IsZero(), Equals, false)
+ //c.Assert(commit.Tree.IsZero(), Equals, false)
+ }
+
+ c.Assert(count, Equals, 8)
}
diff --git a/tree.go b/tree.go
new file mode 100644
index 0000000..192c6a0
--- /dev/null
+++ b/tree.go
@@ -0,0 +1,122 @@
+package git
+
+import (
+ "bufio"
+ "io"
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "gopkg.in/src-d/go-git.v2/internal"
+)
+
+// Tree is basically like a directory - it references a bunch of other trees
+// and/or blobs (i.e. files and sub-directories)
+type Tree struct {
+ Entries []TreeEntry
+ Hash internal.Hash
+
+ r *Repository
+}
+
+// TreeEntry represents a file
+type TreeEntry struct {
+ Name string
+ Mode os.FileMode
+ Hash internal.Hash
+}
+
+func (t *Tree) Files() chan *File {
+ ch := make(chan *File, 1)
+
+ go func() {
+ defer func() { close(ch) }()
+ t.walkEntries("", ch)
+ }()
+
+ return ch
+}
+
+func (t *Tree) walkEntries(base string, ch chan *File) {
+ for _, entry := range t.Entries {
+ obj, _ := t.r.Storage.Get(entry.Hash)
+ if obj.Type() == internal.TreeObject {
+ tree := &Tree{r: t.r}
+ tree.Decode(obj)
+ tree.walkEntries(filepath.Join(base, entry.Name), ch)
+ continue
+ }
+
+ blob := &Blob{}
+ blob.Decode(obj)
+
+ ch <- &File{Name: filepath.Join(base, entry.Name), Reader: blob.Reader()}
+ }
+}
+
+// Decode transform an internal.Object into a Tree struct
+func (t *Tree) Decode(o internal.Object) error {
+ t.Hash = o.Hash()
+ if o.Size() == 0 {
+ return nil
+ }
+
+ r := bufio.NewReader(o.Reader())
+ for {
+ mode, err := r.ReadString(' ')
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+
+ return err
+ }
+
+ fm, err := strconv.ParseInt(mode[:len(mode)-1], 8, 32)
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ name, err := r.ReadString(0)
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ var hash internal.Hash
+ _, err = r.Read(hash[:])
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ t.Entries = append(t.Entries, TreeEntry{
+ Hash: hash,
+ Mode: os.FileMode(fm),
+ Name: name[:len(name)-1],
+ })
+ }
+
+ return nil
+}
+
+type TreeIter struct {
+ iter
+}
+
+func NewTreeIter(r *Repository) *TreeIter {
+ return &TreeIter{newIter(r)}
+}
+
+func (i *TreeIter) Next() (*Tree, error) {
+ obj := <-i.ch
+ if obj == nil {
+ return nil, io.EOF
+ }
+
+ tree := &Tree{r: i.r}
+ return tree, tree.Decode(obj)
+}
+
+type File struct {
+ Name string
+ io.Reader
+}