diff options
author | Santiago M. Mola <santi@mola.io> | 2016-08-29 22:39:08 +0200 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2016-08-29 22:39:08 +0200 |
commit | 5cf20a4edf7803458a1c2ec94e902369bed76f28 (patch) | |
tree | b6a0276ab12f82383818892064038fb0b79e161b | |
parent | a97ca42cbce377b5725ecc41e4539fc7e263b90d (diff) | |
download | go-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.go | 38 | ||||
-rw-r--r-- | commit_test.go | 37 | ||||
-rw-r--r-- | core/memory.go | 1 | ||||
-rw-r--r-- | objects.go | 35 | ||||
-rw-r--r-- | objects_test.go | 32 | ||||
-rw-r--r-- | tag.go | 25 | ||||
-rw-r--r-- | tag_test.go | 30 | ||||
-rw-r--r-- | tree.go | 23 | ||||
-rw-r--r-- | tree_test.go | 23 |
9 files changed, 240 insertions, 4 deletions
@@ -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 } @@ -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`: { @@ -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) @@ -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 |