aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSantiago M. Mola <santi@mola.io>2016-08-29 22:39:08 +0200
committerMáximo Cuadros <mcuadros@gmail.com>2016-08-29 22:39:08 +0200
commit5cf20a4edf7803458a1c2ec94e902369bed76f28 (patch)
treeb6a0276ab12f82383818892064038fb0b79e161b
parenta97ca42cbce377b5725ecc41e4539fc7e263b90d (diff)
downloadgo-git-5cf20a4edf7803458a1c2ec94e902369bed76f28.tar.gz
object: Add Encode method to all objects. (#70)
Encode method encodes a typed object (commit, tree, tag, blob) into raw core.Object representation. Additionally, Decode does not trim commit message lines. This is needed for Decode/Encode to be idempotent.
-rw-r--r--commit.go38
-rw-r--r--commit_test.go37
-rw-r--r--core/memory.go1
-rw-r--r--objects.go35
-rw-r--r--objects_test.go32
-rw-r--r--tag.go25
-rw-r--r--tag_test.go30
-rw-r--r--tree.go23
-rw-r--r--tree_test.go23
9 files changed, 240 insertions, 4 deletions
diff --git a/commit.go b/commit.go
index 4a4fe25..f102796 100644
--- a/commit.go
+++ b/commit.go
@@ -97,8 +97,8 @@ func (c *Commit) Decode(o core.Object) (err error) {
return err
}
- line = bytes.TrimSpace(line)
if !message {
+ line = bytes.TrimSpace(line)
if len(line) == 0 {
message = true
continue
@@ -116,7 +116,7 @@ func (c *Commit) Decode(o core.Object) (err error) {
c.Committer.Decode(split[1])
}
} else {
- c.Message += string(line) + "\n"
+ c.Message += string(line)
}
if err == io.EOF {
@@ -137,6 +137,40 @@ func (c *Commit) History() ([]*Commit, error) {
return commits, err
}
+// Encode transforms a Commit into a core.Object.
+func (b *Commit) Encode(o core.Object) error {
+ o.SetType(core.CommitObject)
+ w, err := o.Writer()
+ if err != nil {
+ return err
+ }
+ defer checkClose(w, &err)
+ if _, err = fmt.Fprintf(w, "tree %s\n", b.tree.String()); err != nil {
+ return err
+ }
+ for _, parent := range b.parents {
+ if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
+ return err
+ }
+ }
+ if _, err = fmt.Fprint(w, "author "); err != nil {
+ return err
+ }
+ if err = b.Author.Encode(w); err != nil {
+ return err
+ }
+ if _, err = fmt.Fprint(w, "\ncommitter "); err != nil {
+ return err
+ }
+ if err = b.Committer.Encode(w); err != nil {
+ return err
+ }
+ if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil {
+ return err
+ }
+ return err
+}
+
func (c *Commit) String() string {
return fmt.Sprintf(
"%s %s\nAuthor: %s\nDate: %s\n",
diff --git a/commit_test.go b/commit_test.go
index 626fdcf..886a61d 100644
--- a/commit_test.go
+++ b/commit_test.go
@@ -2,6 +2,7 @@ package git
import (
"io"
+ "time"
"gopkg.in/src-d/go-git.v4/core"
@@ -62,6 +63,42 @@ func (s *SuiteCommit) TestParents(c *C) {
c.Assert(output, DeepEquals, expected)
}
+func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) {
+ ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00")
+ c.Assert(err, IsNil)
+ commits := []*Commit{
+ &Commit{
+ Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
+ Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts},
+ Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n",
+ tree: core.NewHash("f000000000000000000000000000000000000001"),
+ parents: []core.Hash{core.NewHash("f000000000000000000000000000000000000002")},
+ },
+ &Commit{
+ Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
+ Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts},
+ Message: "Message\n\nFoo\nBar\nWith no trailing blank lines",
+ tree: core.NewHash("0000000000000000000000000000000000000003"),
+ parents: []core.Hash{
+ core.NewHash("f000000000000000000000000000000000000004"),
+ core.NewHash("f000000000000000000000000000000000000005"),
+ core.NewHash("f000000000000000000000000000000000000006"),
+ core.NewHash("f000000000000000000000000000000000000007"),
+ },
+ },
+ }
+ for _, commit := range commits {
+ obj := &core.MemoryObject{}
+ err = commit.Encode(obj)
+ c.Assert(err, IsNil)
+ newCommit := &Commit{}
+ err = newCommit.Decode(obj)
+ c.Assert(err, IsNil)
+ commit.Hash = obj.Hash()
+ c.Assert(newCommit, DeepEquals, commit)
+ }
+}
+
func (s *SuiteCommit) TestFile(c *C) {
file, err := s.Commit.File("CHANGELOG")
c.Assert(err, IsNil)
diff --git a/core/memory.go b/core/memory.go
index 3d8063d..8826966 100644
--- a/core/memory.go
+++ b/core/memory.go
@@ -50,6 +50,7 @@ func (o *MemoryObject) Writer() (ObjectWriter, error) {
func (o *MemoryObject) Write(p []byte) (n int, err error) {
o.cont = append(o.cont, p...)
+ o.sz = int64(len(o.cont))
return len(p), nil
}
diff --git a/objects.go b/objects.go
index 6f3549b..fd79deb 100644
--- a/objects.go
+++ b/objects.go
@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
+ "io"
"strconv"
"time"
@@ -39,6 +40,7 @@ type Object interface {
ID() core.Hash
Type() core.ObjectType
Decode(core.Object) error
+ Encode(core.Object) error
}
// Blob is used to store file data - it is generally a file.
@@ -77,6 +79,23 @@ func (b *Blob) Decode(o core.Object) error {
return nil
}
+// Encode transforms a Blob into a core.Object.
+func (b *Blob) Encode(o core.Object) error {
+ w, err := o.Writer()
+ if err != nil {
+ return err
+ }
+ defer checkClose(w, &err)
+ r, err := b.Reader()
+ if err != nil {
+ return err
+ }
+ defer checkClose(r, &err)
+ _, err = io.Copy(w, r)
+ o.SetType(core.BlobObject)
+ return err
+}
+
// Reader returns a reader allow the access to the content of the blob
func (b *Blob) Reader() (core.ObjectReader, error) {
return b.obj.Reader()
@@ -106,6 +125,17 @@ func (s *Signature) Decode(b []byte) {
}
}
+// Encode encodes a Signature into a writer.
+func (s *Signature) Encode(w io.Writer) error {
+ if _, err := fmt.Fprintf(w, "%s <%s> ", s.Name, s.Email); err != nil {
+ return err
+ }
+ if err := s.encodeTimeAndTimeZone(w); err != nil {
+ return err
+ }
+ return nil
+}
+
var timeZoneLength = 5
func (s *Signature) decodeTimeAndTimeZone(b []byte) {
@@ -133,6 +163,11 @@ func (s *Signature) decodeTimeAndTimeZone(b []byte) {
s.When = s.When.In(tl.Location())
}
+func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error {
+ _, err := fmt.Fprintf(w, "%d %s", s.When.Unix(), s.When.Format("-0700"))
+ return err
+}
+
func (s *Signature) String() string {
return fmt.Sprintf("%s <%s>", s.Name, s.Email)
}
diff --git a/objects_test.go b/objects_test.go
index df62fe1..da9dced 100644
--- a/objects_test.go
+++ b/objects_test.go
@@ -9,9 +9,14 @@ import (
. "gopkg.in/check.v1"
)
+var fixtures = []packedFixture{
+ {"https://github.com/spinnaker/spinnaker.git", "formats/packfile/fixtures/spinnaker-spinnaker.pack"},
+}
+
type ObjectsSuite struct {
BaseSuite
- r *Repository
+ r *Repository
+ repos map[string]*Repository
}
var _ = Suite(&ObjectsSuite{})
@@ -22,6 +27,8 @@ func (s *ObjectsSuite) SetUpSuite(c *C) {
s.r = NewMemoryRepository()
err := s.r.Clone(&CloneOptions{URL: RepositoryFixture})
c.Assert(err, IsNil)
+
+ s.repos = unpackFixtures(c, tagFixtures)
}
func (s *ObjectsSuite) TestNewCommit(c *C) {
@@ -49,7 +56,7 @@ func (s *ObjectsSuite) TestNewCommit(c *C) {
c.Assert(commit.Author.Name, Equals, "Máximo Cuadros")
c.Assert(commit.Author.When.Format(time.RFC3339), Equals, "2015-03-31T13:47:14+02:00")
c.Assert(commit.Committer.Email, Equals, "mcuadros@gmail.com")
- c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog\n")
+ c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog")
}
func (s *ObjectsSuite) TestParseTree(c *C) {
@@ -108,6 +115,27 @@ func (s *ObjectsSuite) TestBlobHash(c *C) {
c.Assert(string(data), Equals, "FOO")
}
+func (s *ObjectsSuite) TestBlobDecodeEncodeIdempotent(c *C) {
+ var objects []*core.MemoryObject
+ for _, str := range []string{"foo", "foo\n"} {
+ obj := &core.MemoryObject{}
+ obj.Write([]byte(str))
+ obj.SetType(core.BlobObject)
+ obj.Hash()
+ objects = append(objects, obj)
+ }
+ for _, object := range objects {
+ blob := &Blob{}
+ err := blob.Decode(object)
+ c.Assert(err, IsNil)
+ newObject := &core.MemoryObject{}
+ err = blob.Encode(newObject)
+ c.Assert(err, IsNil)
+ newObject.Hash() // Ensure Hash is pre-computed before deep comparison
+ c.Assert(newObject, DeepEquals, object)
+ }
+}
+
func (s *ObjectsSuite) TestParseSignature(c *C) {
cases := map[string]Signature{
`Foo Bar <foo@bar.com> 1257894000 +0100`: {
diff --git a/tag.go b/tag.go
index fec2abf..1391fad 100644
--- a/tag.go
+++ b/tag.go
@@ -105,6 +105,31 @@ func (t *Tag) Decode(o core.Object) (err error) {
return nil
}
+// Encode transforms a Tag into a core.Object.
+func (t *Tag) Encode(o core.Object) error {
+ o.SetType(core.TagObject)
+ w, err := o.Writer()
+ if err != nil {
+ return err
+ }
+ defer checkClose(w, &err)
+ if _, err = fmt.Fprintf(w,
+ "object %s\ntype %s\ntag %s\ntagger ",
+ t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
+ return err
+ }
+ if err = t.Tagger.Encode(w); err != nil {
+ return err
+ }
+ if _, err = fmt.Fprint(w, "\n\n"); err != nil {
+ return err
+ }
+ if _, err = fmt.Fprint(w, t.Message); err != nil {
+ return err
+ }
+ return err
+}
+
// Commit returns the commit pointed to by the tag. If the tag points to a
// different type of object ErrUnsupportedObject will be returned.
func (t *Tag) Commit() (*Commit, error) {
diff --git a/tag_test.go b/tag_test.go
index a66cac1..ac4763b 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -139,6 +139,36 @@ func (s *SuiteTag) TestObject(c *C) {
}
}
+func (s *SuiteTag) TestTagEncodeDecodeIdempotent(c *C) {
+ ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00")
+ c.Assert(err, IsNil)
+ tags := []*Tag{
+ &Tag{
+ Name: "foo",
+ Tagger: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
+ Message: "Message\n\nFoo\nBar\nBaz\n\n",
+ TargetType: core.BlobObject,
+ Target: core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
+ },
+ &Tag{
+ Name: "foo",
+ Tagger: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
+ TargetType: core.BlobObject,
+ Target: core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
+ },
+ }
+ for _, tag := range tags {
+ obj := &core.MemoryObject{}
+ err = tag.Encode(obj)
+ c.Assert(err, IsNil)
+ newTag := &Tag{}
+ err = newTag.Decode(obj)
+ c.Assert(err, IsNil)
+ tag.Hash = obj.Hash()
+ c.Assert(newTag, DeepEquals, tag)
+ }
+}
+
func testTagExpected(c *C, tag *Tag, hash core.Hash, exp expectedTag, com string) {
when, err := time.Parse(time.RFC3339, exp.When)
c.Assert(err, IsNil)
diff --git a/tree.go b/tree.go
index cfe457e..86f1aaa 100644
--- a/tree.go
+++ b/tree.go
@@ -3,6 +3,7 @@ package git
import (
"bufio"
"errors"
+ "fmt"
"io"
"os"
"strconv"
@@ -210,6 +211,28 @@ func (t *Tree) decodeFileMode(mode string) (os.FileMode, error) {
return m, nil
}
+// Encode transforms a Tree into a core.Object.
+func (t *Tree) Encode(o core.Object) error {
+ o.SetType(core.TreeObject)
+ w, err := o.Writer()
+ if err != nil {
+ return err
+ }
+ defer checkClose(w, &err)
+ for _, entry := range t.Entries {
+ if _, err = fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil {
+ return err
+ }
+ if _, err = w.Write([]byte{0x00}); err != nil {
+ return err
+ }
+ if _, err = w.Write([]byte(entry.Hash[:])); err != nil {
+ return err
+ }
+ }
+ return err
+}
+
func (t *Tree) buildMap() {
t.m = make(map[string]*TreeEntry)
for i := 0; i < len(t.Entries); i++ {
diff --git a/tree_test.go b/tree_test.go
index cf1ebdd..5f285af 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -2,6 +2,7 @@ package git
import (
"io"
+ "os"
"gopkg.in/src-d/go-git.v4/core"
@@ -1254,6 +1255,28 @@ func (s *SuiteTree) TestTreeDecodeReadBug(c *C) {
c.Assert(EntriesEquals(obtained.Entries, expected.Entries), Equals, true)
}
+func (s *SuiteTree) TestTreeDecodeEncodeIdempotent(c *C) {
+ trees := []*Tree{
+ &Tree{
+ Entries: []TreeEntry{
+ TreeEntry{"foo", os.FileMode(0), core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ TreeEntry{"bar", os.FileMode(0), core.NewHash("c029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ TreeEntry{"baz", os.FileMode(0), core.NewHash("d029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ },
+ },
+ }
+ for _, tree := range trees {
+ obj := &core.MemoryObject{}
+ err := tree.Encode(obj)
+ c.Assert(err, IsNil)
+ newTree := &Tree{}
+ err = newTree.Decode(obj)
+ c.Assert(err, IsNil)
+ tree.Hash = obj.Hash()
+ c.Assert(newTree, DeepEquals, tree)
+ }
+}
+
func EntriesEquals(a, b []TreeEntry) bool {
if a == nil && b == nil {
return true