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

Commit cac95dd

Browse files
smolamcuadros
authored andcommitted
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.
1 parent 4a3fa49 commit cac95dd

File tree

9 files changed

+240
-4
lines changed

9 files changed

+240
-4
lines changed

commit.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ func (c *Commit) Decode(o core.Object) (err error) {
9797
return err
9898
}
9999

100-
line = bytes.TrimSpace(line)
101100
if !message {
101+
line = bytes.TrimSpace(line)
102102
if len(line) == 0 {
103103
message = true
104104
continue
@@ -116,7 +116,7 @@ func (c *Commit) Decode(o core.Object) (err error) {
116116
c.Committer.Decode(split[1])
117117
}
118118
} else {
119-
c.Message += string(line) + "\n"
119+
c.Message += string(line)
120120
}
121121

122122
if err == io.EOF {
@@ -137,6 +137,40 @@ func (c *Commit) History() ([]*Commit, error) {
137137
return commits, err
138138
}
139139

140+
// Encode transforms a Commit into a core.Object.
141+
func (b *Commit) Encode(o core.Object) error {
142+
o.SetType(core.CommitObject)
143+
w, err := o.Writer()
144+
if err != nil {
145+
return err
146+
}
147+
defer checkClose(w, &err)
148+
if _, err = fmt.Fprintf(w, "tree %s\n", b.tree.String()); err != nil {
149+
return err
150+
}
151+
for _, parent := range b.parents {
152+
if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
153+
return err
154+
}
155+
}
156+
if _, err = fmt.Fprint(w, "author "); err != nil {
157+
return err
158+
}
159+
if err = b.Author.Encode(w); err != nil {
160+
return err
161+
}
162+
if _, err = fmt.Fprint(w, "\ncommitter "); err != nil {
163+
return err
164+
}
165+
if err = b.Committer.Encode(w); err != nil {
166+
return err
167+
}
168+
if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil {
169+
return err
170+
}
171+
return err
172+
}
173+
140174
func (c *Commit) String() string {
141175
return fmt.Sprintf(
142176
"%s %s\nAuthor: %s\nDate: %s\n",

commit_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package git
22

33
import (
44
"io"
5+
"time"
56

67
"gopkg.in/src-d/go-git.v4/core"
78

@@ -62,6 +63,42 @@ func (s *SuiteCommit) TestParents(c *C) {
6263
c.Assert(output, DeepEquals, expected)
6364
}
6465

66+
func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) {
67+
ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00")
68+
c.Assert(err, IsNil)
69+
commits := []*Commit{
70+
&Commit{
71+
Author: Signature{Name: "Foo", Email: "[email protected]", When: ts},
72+
Committer: Signature{Name: "Bar", Email: "[email protected]", When: ts},
73+
Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n",
74+
tree: core.NewHash("f000000000000000000000000000000000000001"),
75+
parents: []core.Hash{core.NewHash("f000000000000000000000000000000000000002")},
76+
},
77+
&Commit{
78+
Author: Signature{Name: "Foo", Email: "[email protected]", When: ts},
79+
Committer: Signature{Name: "Bar", Email: "[email protected]", When: ts},
80+
Message: "Message\n\nFoo\nBar\nWith no trailing blank lines",
81+
tree: core.NewHash("0000000000000000000000000000000000000003"),
82+
parents: []core.Hash{
83+
core.NewHash("f000000000000000000000000000000000000004"),
84+
core.NewHash("f000000000000000000000000000000000000005"),
85+
core.NewHash("f000000000000000000000000000000000000006"),
86+
core.NewHash("f000000000000000000000000000000000000007"),
87+
},
88+
},
89+
}
90+
for _, commit := range commits {
91+
obj := &core.MemoryObject{}
92+
err = commit.Encode(obj)
93+
c.Assert(err, IsNil)
94+
newCommit := &Commit{}
95+
err = newCommit.Decode(obj)
96+
c.Assert(err, IsNil)
97+
commit.Hash = obj.Hash()
98+
c.Assert(newCommit, DeepEquals, commit)
99+
}
100+
}
101+
65102
func (s *SuiteCommit) TestFile(c *C) {
66103
file, err := s.Commit.File("CHANGELOG")
67104
c.Assert(err, IsNil)

core/memory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func (o *MemoryObject) Writer() (ObjectWriter, error) {
5050

5151
func (o *MemoryObject) Write(p []byte) (n int, err error) {
5252
o.cont = append(o.cont, p...)
53+
o.sz = int64(len(o.cont))
5354
return len(p), nil
5455
}
5556

objects.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"io"
78
"strconv"
89
"time"
910

@@ -39,6 +40,7 @@ type Object interface {
3940
ID() core.Hash
4041
Type() core.ObjectType
4142
Decode(core.Object) error
43+
Encode(core.Object) error
4244
}
4345

4446
// Blob is used to store file data - it is generally a file.
@@ -77,6 +79,23 @@ func (b *Blob) Decode(o core.Object) error {
7779
return nil
7880
}
7981

82+
// Encode transforms a Blob into a core.Object.
83+
func (b *Blob) Encode(o core.Object) error {
84+
w, err := o.Writer()
85+
if err != nil {
86+
return err
87+
}
88+
defer checkClose(w, &err)
89+
r, err := b.Reader()
90+
if err != nil {
91+
return err
92+
}
93+
defer checkClose(r, &err)
94+
_, err = io.Copy(w, r)
95+
o.SetType(core.BlobObject)
96+
return err
97+
}
98+
8099
// Reader returns a reader allow the access to the content of the blob
81100
func (b *Blob) Reader() (core.ObjectReader, error) {
82101
return b.obj.Reader()
@@ -106,6 +125,17 @@ func (s *Signature) Decode(b []byte) {
106125
}
107126
}
108127

128+
// Encode encodes a Signature into a writer.
129+
func (s *Signature) Encode(w io.Writer) error {
130+
if _, err := fmt.Fprintf(w, "%s <%s> ", s.Name, s.Email); err != nil {
131+
return err
132+
}
133+
if err := s.encodeTimeAndTimeZone(w); err != nil {
134+
return err
135+
}
136+
return nil
137+
}
138+
109139
var timeZoneLength = 5
110140

111141
func (s *Signature) decodeTimeAndTimeZone(b []byte) {
@@ -133,6 +163,11 @@ func (s *Signature) decodeTimeAndTimeZone(b []byte) {
133163
s.When = s.When.In(tl.Location())
134164
}
135165

166+
func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error {
167+
_, err := fmt.Fprintf(w, "%d %s", s.When.Unix(), s.When.Format("-0700"))
168+
return err
169+
}
170+
136171
func (s *Signature) String() string {
137172
return fmt.Sprintf("%s <%s>", s.Name, s.Email)
138173
}

objects_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ import (
99
. "gopkg.in/check.v1"
1010
)
1111

12+
var fixtures = []packedFixture{
13+
{"https://github.com/spinnaker/spinnaker.git", "formats/packfile/fixtures/spinnaker-spinnaker.pack"},
14+
}
15+
1216
type ObjectsSuite struct {
1317
BaseSuite
14-
r *Repository
18+
r *Repository
19+
repos map[string]*Repository
1520
}
1621

1722
var _ = Suite(&ObjectsSuite{})
@@ -22,6 +27,8 @@ func (s *ObjectsSuite) SetUpSuite(c *C) {
2227
s.r = NewMemoryRepository()
2328
err := s.r.Clone(&CloneOptions{URL: RepositoryFixture})
2429
c.Assert(err, IsNil)
30+
31+
s.repos = unpackFixtures(c, tagFixtures)
2532
}
2633

2734
func (s *ObjectsSuite) TestNewCommit(c *C) {
@@ -49,7 +56,7 @@ func (s *ObjectsSuite) TestNewCommit(c *C) {
4956
c.Assert(commit.Author.Name, Equals, "Máximo Cuadros")
5057
c.Assert(commit.Author.When.Format(time.RFC3339), Equals, "2015-03-31T13:47:14+02:00")
5158
c.Assert(commit.Committer.Email, Equals, "[email protected]")
52-
c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog\n")
59+
c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog")
5360
}
5461

5562
func (s *ObjectsSuite) TestParseTree(c *C) {
@@ -108,6 +115,27 @@ func (s *ObjectsSuite) TestBlobHash(c *C) {
108115
c.Assert(string(data), Equals, "FOO")
109116
}
110117

118+
func (s *ObjectsSuite) TestBlobDecodeEncodeIdempotent(c *C) {
119+
var objects []*core.MemoryObject
120+
for _, str := range []string{"foo", "foo\n"} {
121+
obj := &core.MemoryObject{}
122+
obj.Write([]byte(str))
123+
obj.SetType(core.BlobObject)
124+
obj.Hash()
125+
objects = append(objects, obj)
126+
}
127+
for _, object := range objects {
128+
blob := &Blob{}
129+
err := blob.Decode(object)
130+
c.Assert(err, IsNil)
131+
newObject := &core.MemoryObject{}
132+
err = blob.Encode(newObject)
133+
c.Assert(err, IsNil)
134+
newObject.Hash() // Ensure Hash is pre-computed before deep comparison
135+
c.Assert(newObject, DeepEquals, object)
136+
}
137+
}
138+
111139
func (s *ObjectsSuite) TestParseSignature(c *C) {
112140
cases := map[string]Signature{
113141
`Foo Bar <[email protected]> 1257894000 +0100`: {

tag.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,31 @@ func (t *Tag) Decode(o core.Object) (err error) {
105105
return nil
106106
}
107107

108+
// Encode transforms a Tag into a core.Object.
109+
func (t *Tag) Encode(o core.Object) error {
110+
o.SetType(core.TagObject)
111+
w, err := o.Writer()
112+
if err != nil {
113+
return err
114+
}
115+
defer checkClose(w, &err)
116+
if _, err = fmt.Fprintf(w,
117+
"object %s\ntype %s\ntag %s\ntagger ",
118+
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
119+
return err
120+
}
121+
if err = t.Tagger.Encode(w); err != nil {
122+
return err
123+
}
124+
if _, err = fmt.Fprint(w, "\n\n"); err != nil {
125+
return err
126+
}
127+
if _, err = fmt.Fprint(w, t.Message); err != nil {
128+
return err
129+
}
130+
return err
131+
}
132+
108133
// Commit returns the commit pointed to by the tag. If the tag points to a
109134
// different type of object ErrUnsupportedObject will be returned.
110135
func (t *Tag) Commit() (*Commit, error) {

tag_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,36 @@ func (s *SuiteTag) TestObject(c *C) {
139139
}
140140
}
141141

142+
func (s *SuiteTag) TestTagEncodeDecodeIdempotent(c *C) {
143+
ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00")
144+
c.Assert(err, IsNil)
145+
tags := []*Tag{
146+
&Tag{
147+
Name: "foo",
148+
Tagger: Signature{Name: "Foo", Email: "[email protected]", When: ts},
149+
Message: "Message\n\nFoo\nBar\nBaz\n\n",
150+
TargetType: core.BlobObject,
151+
Target: core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
152+
},
153+
&Tag{
154+
Name: "foo",
155+
Tagger: Signature{Name: "Foo", Email: "[email protected]", When: ts},
156+
TargetType: core.BlobObject,
157+
Target: core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
158+
},
159+
}
160+
for _, tag := range tags {
161+
obj := &core.MemoryObject{}
162+
err = tag.Encode(obj)
163+
c.Assert(err, IsNil)
164+
newTag := &Tag{}
165+
err = newTag.Decode(obj)
166+
c.Assert(err, IsNil)
167+
tag.Hash = obj.Hash()
168+
c.Assert(newTag, DeepEquals, tag)
169+
}
170+
}
171+
142172
func testTagExpected(c *C, tag *Tag, hash core.Hash, exp expectedTag, com string) {
143173
when, err := time.Parse(time.RFC3339, exp.When)
144174
c.Assert(err, IsNil)

tree.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package git
33
import (
44
"bufio"
55
"errors"
6+
"fmt"
67
"io"
78
"os"
89
"strconv"
@@ -210,6 +211,28 @@ func (t *Tree) decodeFileMode(mode string) (os.FileMode, error) {
210211
return m, nil
211212
}
212213

214+
// Encode transforms a Tree into a core.Object.
215+
func (t *Tree) Encode(o core.Object) error {
216+
o.SetType(core.TreeObject)
217+
w, err := o.Writer()
218+
if err != nil {
219+
return err
220+
}
221+
defer checkClose(w, &err)
222+
for _, entry := range t.Entries {
223+
if _, err = fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil {
224+
return err
225+
}
226+
if _, err = w.Write([]byte{0x00}); err != nil {
227+
return err
228+
}
229+
if _, err = w.Write([]byte(entry.Hash[:])); err != nil {
230+
return err
231+
}
232+
}
233+
return err
234+
}
235+
213236
func (t *Tree) buildMap() {
214237
t.m = make(map[string]*TreeEntry)
215238
for i := 0; i < len(t.Entries); i++ {

tree_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package git
22

33
import (
44
"io"
5+
"os"
56

67
"gopkg.in/src-d/go-git.v4/core"
78

@@ -1254,6 +1255,28 @@ func (s *SuiteTree) TestTreeDecodeReadBug(c *C) {
12541255
c.Assert(EntriesEquals(obtained.Entries, expected.Entries), Equals, true)
12551256
}
12561257

1258+
func (s *SuiteTree) TestTreeDecodeEncodeIdempotent(c *C) {
1259+
trees := []*Tree{
1260+
&Tree{
1261+
Entries: []TreeEntry{
1262+
TreeEntry{"foo", os.FileMode(0), core.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d")},
1263+
TreeEntry{"bar", os.FileMode(0), core.NewHash("c029517f6300c2da0f4b651b8642506cd6aaf45d")},
1264+
TreeEntry{"baz", os.FileMode(0), core.NewHash("d029517f6300c2da0f4b651b8642506cd6aaf45d")},
1265+
},
1266+
},
1267+
}
1268+
for _, tree := range trees {
1269+
obj := &core.MemoryObject{}
1270+
err := tree.Encode(obj)
1271+
c.Assert(err, IsNil)
1272+
newTree := &Tree{}
1273+
err = newTree.Decode(obj)
1274+
c.Assert(err, IsNil)
1275+
tree.Hash = obj.Hash()
1276+
c.Assert(newTree, DeepEquals, tree)
1277+
}
1278+
}
1279+
12571280
func EntriesEquals(a, b []TreeEntry) bool {
12581281
if a == nil && b == nil {
12591282
return true

0 commit comments

Comments
 (0)