diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-27 01:49:58 +0100 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-27 01:49:58 +0100 |
commit | 7d6c5a56c0b63705378f125523876de1a97fd1ce (patch) | |
tree | 8945a8a02d53f36a64304beaf006c4f46d61da48 | |
parent | a2e49a59782a50a9ff116c6d17c6e3888502f2ad (diff) | |
download | go-git-7d6c5a56c0b63705378f125523876de1a97fd1ce.tar.gz |
tree and commit
-rw-r--r-- | commit.go | 127 | ||||
-rw-r--r-- | common_test.go | 31 | ||||
-rw-r--r-- | internal/hash.go | 5 | ||||
-rw-r--r-- | internal/hash_test.go | 8 | ||||
-rw-r--r-- | internal/object.go | 18 | ||||
-rw-r--r-- | objects.go | 143 | ||||
-rw-r--r-- | objects_test.go | 68 | ||||
-rw-r--r-- | remote_test.go | 6 | ||||
-rw-r--r-- | repository.go | 40 | ||||
-rw-r--r-- | repository_test.go | 46 | ||||
-rw-r--r-- | tree.go | 122 |
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: @@ -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) } @@ -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 +} |