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

Commit 9c9cdff

Browse files
committed
Merge pull request #37 from scjalliance/objfile-format
Added support for objfile format
2 parents 1e74b17 + 31f920a commit 9c9cdff

File tree

15 files changed

+610
-19
lines changed

15 files changed

+610
-19
lines changed

commit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (c *Commit) Decode(o core.Object) (err error) {
6161
if err != nil {
6262
return err
6363
}
64-
defer close(reader, &err)
64+
defer checkClose(reader, &err)
6565

6666
r := bufio.NewReader(reader)
6767

common.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,21 @@ func countLines(s string) int {
2222
return nEOL + 1
2323
}
2424

25-
// close is used with defer to close the given io.Closer and check its
25+
// checkClose is used with defer to close the given io.Closer and check its
2626
// returned error value. If Close returns an error and the given *error
2727
// is not nil, *error is set to the error returned by Close.
2828
//
29-
// close is typically used with named return values like so:
29+
// checkClose is typically used with named return values like so:
3030
//
3131
// func do(obj *Object) (err error) {
3232
// w, err := obj.Writer()
3333
// if err != nil {
3434
// return nil
3535
// }
36-
// defer close(w, &err)
36+
// defer checkClose(w, &err)
3737
// // work with w
3838
// }
39-
func close(c io.Closer, err *error) {
39+
func checkClose(c io.Closer, err *error) {
4040
if cerr := c.Close(); cerr != nil && *err == nil {
4141
*err = cerr
4242
}

core/object.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
var (
1010
ObjectNotFoundErr = errors.New("object not found")
11+
// ErrInvalidType is returned when an invalid object type is provided.
12+
ErrInvalidType = errors.New("invalid object type")
1113
)
1214

1315
// TODO: Consider adding a Hash function to the ObjectReader and ObjectWriter
@@ -79,6 +81,11 @@ func (t ObjectType) Bytes() []byte {
7981
return []byte(t.String())
8082
}
8183

84+
// Valid returns true if t is a valid ObjectType.
85+
func (t ObjectType) Valid() bool {
86+
return t >= CommitObject && t <= REFDeltaObject
87+
}
88+
8289
// ParseObjectType parses a string representation of ObjectType. It returns an
8390
// error on parse failure.
8491
func ParseObjectType(value string) (typ ObjectType, err error) {
@@ -96,7 +103,7 @@ func ParseObjectType(value string) (typ ObjectType, err error) {
96103
case "ref-delta":
97104
typ = REFDeltaObject
98105
default:
99-
err = errors.New("unable to parse object type")
106+
err = ErrInvalidType
100107
}
101108
return
102109
}

file.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (f *File) Contents() (content string, err error) {
2323
if err != nil {
2424
return "", err
2525
}
26-
defer close(reader, &err)
26+
defer checkClose(reader, &err)
2727

2828
buf := new(bytes.Buffer)
2929
buf.ReadFrom(reader)

formats/objfile/common.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package objfile
2+
3+
import (
4+
"errors"
5+
"io"
6+
"strconv"
7+
8+
"gopkg.in/src-d/go-git.v3/core"
9+
)
10+
11+
var (
12+
// ErrClosed is returned when the objfile Reader or Writer is already closed.
13+
ErrClosed = errors.New("objfile: already closed")
14+
// ErrHeader is returned when the objfile has an invalid header.
15+
ErrHeader = errors.New("objfile: invalid header")
16+
// ErrNegativeSize is returned when a negative object size is declared.
17+
ErrNegativeSize = errors.New("objfile: negative object size")
18+
)
19+
20+
type header struct {
21+
t core.ObjectType
22+
size int64
23+
}
24+
25+
func (h *header) Read(r io.Reader) error {
26+
t, err := h.readSlice(r, ' ')
27+
if err != nil {
28+
return err
29+
}
30+
31+
h.t, err = core.ParseObjectType(string(t))
32+
if err != nil {
33+
return err
34+
}
35+
36+
size, err := h.readSlice(r, 0)
37+
if err != nil {
38+
return err
39+
}
40+
41+
h.size, err = strconv.ParseInt(string(size), 10, 64)
42+
if err != nil {
43+
return ErrHeader
44+
}
45+
46+
if h.size < 0 {
47+
return ErrNegativeSize
48+
}
49+
50+
return nil
51+
}
52+
53+
func (h *header) Write(w io.Writer) error {
54+
b := h.t.Bytes()
55+
b = append(b, ' ')
56+
b = append(b, []byte(strconv.FormatInt(h.size, 10))...)
57+
b = append(b, 0)
58+
_, err := w.Write(b)
59+
return err
60+
}
61+
62+
// readSlice reads one byte at a time from r until it encounters delim or an
63+
// error.
64+
func (h *header) readSlice(r io.Reader, delim byte) ([]byte, error) {
65+
var buf [1]byte
66+
value := make([]byte, 0, 16)
67+
for {
68+
if n, err := r.Read(buf[:]); err != nil && (err != io.EOF || n == 0) {
69+
if err == io.EOF {
70+
return nil, ErrHeader
71+
}
72+
return nil, err
73+
}
74+
if buf[0] == delim {
75+
return value, nil
76+
}
77+
value = append(value, buf[0])
78+
}
79+
}

formats/objfile/common_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package objfile
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"testing"
7+
8+
. "gopkg.in/check.v1"
9+
"gopkg.in/src-d/go-git.v3/core"
10+
)
11+
12+
type objfileFixture struct {
13+
hash string // hash of data
14+
t core.ObjectType // object type
15+
content string // base64-encoded content
16+
data string // base64-encoded objfile data
17+
}
18+
19+
var objfileFixtures = []objfileFixture{
20+
{
21+
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
22+
core.BlobObject,
23+
base64.StdEncoding.EncodeToString([]byte("")),
24+
"eAFLyslPUjBgAAAJsAHw",
25+
},
26+
{
27+
"a8a940627d132695a9769df883f85992f0ff4a43",
28+
core.BlobObject,
29+
base64.StdEncoding.EncodeToString([]byte("this is a test")),
30+
"eAFLyslPUjA0YSjJyCxWAKJEhZLU4hIAUDYHOg==",
31+
},
32+
{
33+
"4dc2174801ac4a3d36886210fd086fbe134cf7b2",
34+
core.BlobObject,
35+
base64.StdEncoding.EncodeToString([]byte("this\nis\n\n\na\nmultiline\n\ntest.\n")),
36+
"eAFLyslPUjCyZCjJyCzmAiIurkSu3NKcksyczLxULq6S1OISPS4A1I8LMQ==",
37+
},
38+
{
39+
"13e6f47dd57798bfdc728d91f5c6d7f40c5bb5fc",
40+
core.BlobObject,
41+
base64.StdEncoding.EncodeToString([]byte("this tests\r\nCRLF\r\nencoded files.\r\n")),
42+
"eAFLyslPUjA2YSjJyCxWKEktLinm5XIO8nHj5UrNS85PSU1RSMvMSS3W4+UCABp3DNE=",
43+
},
44+
{
45+
"72a7bc4667ab068e954172437b993d9fbaa137cb",
46+
core.BlobObject,
47+
base64.StdEncoding.EncodeToString([]byte("[email protected]")),
48+
"eAFLyslPUjA0YyhJLS5xSK1IzC3ISdVLzs8FAGVtCIA=",
49+
},
50+
{
51+
"bb2b40e85ec0455d1de72daff71583f0dd72a33f",
52+
core.BlobObject,
53+
base64.StdEncoding.EncodeToString([]byte("package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"os\"\r\n\r\n\t\"gopkg.in/src-d/go-git.v3\"\r\n)\r\n\r\nfunc main() {\r\n\tfmt.Printf(\"Retrieving %q ...\\n\", os.Args[2])\r\n\tr, err := git.NewRepository(os.Args[2], nil)\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\tif err := r.Pull(\"origin\", \"refs/heads/master\"); err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\tdumpCommits(r)\r\n}\r\n\r\nfunc dumpCommits(r *git.Repository) {\r\n\titer := r.Commits()\r\n\tdefer iter.Close()\r\n\r\n\tfor {\r\n\t\tcommit, err := iter.Next()\r\n\t\tif err != nil {\r\n\t\t\tif err == io.EOF {\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\r\n\t\t\tpanic(err)\r\n\t\t}\r\n\r\n\t\tfmt.Println(commit)\r\n\t}\r\n}\r\n")),
54+
"eAGNUU1LAzEU9JpC/0NcEFJps2ARQdmDFD3W0qt6SHez8dHdZH1JqyL+d/Oy/aDgQVh47LzJTGayatyKX99MzzpVrpXRvFVgh4PhANrOYeBiOGBZ3YaMJrg0nI+D/o3r1kaCzT2Wkyo3bmIgyO00rkfEqDe2TIJixL/jgagjFwg21CJb6oCgt2ANv3jnUsoXm4258/IejX++eo0CDMdcI/LbgpPuXH8sdec8BIdf4sgccwsN0aFO9POCgGTIOmWhFFGE9j/p1jtWFEW52DSNyByCAXLPUNc+f9Oq8nmrfNCYje7+o1lt2m7m2haCF2SVnFL6kw2/pBzHEH0rEH0oI8q9BF220nWEaSdnjfNaRDDCtcM+WZnsDgUl4lx/BuKxv6rYY0XBwcmHp8deh7EVarWmQ7uC2Glre/TweI0VvTk5xaTx+wWX66Gs",
55+
},
56+
{
57+
"e94db0f9ffca44dc7bade6a3591f544183395a7c",
58+
core.TreeObject,
59+
"MTAwNjQ0IFRlc3QgMS50eHQAqKlAYn0TJpWpdp34g/hZkvD/SkMxMDA2NDQgVGVzdCAyLnR4dABNwhdIAaxKPTaIYhD9CG++E0z3sjEwMDY0NCBUZXN0IDMudHh0ABPm9H3Vd5i/3HKNkfXG1/QMW7X8MTAwNjQ0IFRlc3QgNC50eHQAcqe8RmerBo6VQXJDe5k9n7qhN8sxMDA2NDQgVGVzdCA1LnR4dAC7K0DoXsBFXR3nLa/3FYPw3XKjPw==",
60+
"eAErKUpNVTC0NGAwNDAwMzFRCEktLlEw1CupKGFYsdIhqVZYberKsrk/mn9ETvrw38sZWZURWJXvIXEPxjVetmYdSQJ/OfL3Cft834SsyhisSvjZl9qr5TP23ynqnfj12PUvPNFb/yCrMgGrKlq+xy19NVvfVMci5+qZtvN3LTQ/jazKFKxqt7bDi7gDrrGyz3XXfxdt/nC3aLE9AA2STmk=",
61+
},
62+
{
63+
"9d7f8a56eaf92469dee8a856e716a03387ddb076",
64+
core.CommitObject,
65+
"dHJlZSBlOTRkYjBmOWZmY2E0NGRjN2JhZGU2YTM1OTFmNTQ0MTgzMzk1YTdjCmF1dGhvciBKb3NodWEgU2pvZGluZyA8am9zaHVhLnNqb2RpbmdAc2NqYWxsaWFuY2UuY29tPiAxNDU2NTMxNTgzIC0wODAwCmNvbW1pdHRlciBKb3NodWEgU2pvZGluZyA8am9zaHVhLnNqb2RpbmdAc2NqYWxsaWFuY2UuY29tPiAxNDU2NTMxNTgzIC0wODAwCgpUZXN0IENvbW1pdAo=",
66+
"eAGtjksOgjAUAF33FO8CktZ+aBNjTNy51Qs8Xl8FAjSh5f4SvILLmcVkKM/zUOEi3amuzMDBxE6mkBKhMZHaDiM71DaoZI1RXutgsSWBW+3zCs9c+g3hNeY4LB+4jgc35cf3QiNO04ALcUN5voEy1lmtrNdwll5Ksdt9oPIfUuLNpcLjCIov3ApFmQ==",
67+
},
68+
}
69+
70+
func Test(t *testing.T) { TestingT(t) }
71+
72+
type SuiteCommon struct{}
73+
74+
var _ = Suite(&SuiteCommon{})
75+
76+
func (s *SuiteCommon) TestHeaderReadEmpty(c *C) {
77+
var h header
78+
c.Assert(h.Read(new(bytes.Buffer)), Equals, ErrHeader)
79+
}
80+
81+
func (s *SuiteCommon) TestHeaderReadGarbage(c *C) {
82+
var h header
83+
c.Assert(h.Read(bytes.NewBuffer([]byte{1, 2, 3, 4, 5})), Equals, ErrHeader)
84+
c.Assert(h.Read(bytes.NewBuffer([]byte{1, 2, 3, 4, 5, '0'})), Equals, ErrHeader)
85+
}
86+
87+
func (s *SuiteCommon) TestHeaderReadInvalidType(c *C) {
88+
var h header
89+
c.Assert(h.Read(bytes.NewBuffer([]byte{1, 2, ' ', 4, 5, 0})), Equals, core.ErrInvalidType)
90+
}
91+
92+
func (s *SuiteCommon) TestHeaderReadInvalidSize(c *C) {
93+
var h header
94+
c.Assert(h.Read(bytes.NewBuffer([]byte{'b', 'l', 'o', 'b', ' ', 'a', 0})), Equals, ErrHeader)
95+
}
96+
97+
func (s *SuiteCommon) TestHeaderReadNegativeSize(c *C) {
98+
var h header
99+
c.Assert(h.Read(bytes.NewBuffer([]byte{'b', 'l', 'o', 'b', ' ', '-', '1', 0})), Equals, ErrNegativeSize)
100+
}

formats/objfile/reader.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package objfile
2+
3+
import (
4+
"errors"
5+
"io"
6+
7+
"gopkg.in/src-d/go-git.v3/core"
8+
9+
"github.com/klauspost/compress/zlib"
10+
)
11+
12+
var (
13+
// ErrZLib is returned when the objfile contains invalid zlib data.
14+
ErrZLib = errors.New("objfile: invalid zlib data")
15+
)
16+
17+
// Reader reads and decodes compressed objfile data from a provided io.Reader.
18+
//
19+
// Reader implements io.ReadCloser. Close should be called when finished with
20+
// the Reader. Close will not close the underlying io.Reader.
21+
type Reader struct {
22+
header header
23+
hash core.Hash // final computed hash stored after Close
24+
25+
r io.Reader // provided reader wrapped in decompressor and tee
26+
decompressor io.ReadCloser // provided reader wrapped in decompressor, retained for calling Close
27+
h core.Hasher // streaming SHA1 hash of decoded data
28+
}
29+
30+
// NewReader returns a new Reader reading from r.
31+
//
32+
// Calling NewReader causes it to immediately read in header data from r
33+
// containing size and type information. Any errors encountered in that
34+
// process will be returned in err.
35+
//
36+
// The returned Reader implements io.ReadCloser. Close should be called when
37+
// finished with the Reader. Close will not close the underlying io.Reader.
38+
func NewReader(r io.Reader) (*Reader, error) {
39+
reader := &Reader{}
40+
return reader, reader.init(r)
41+
}
42+
43+
// init prepares the zlib decompressor for the given input as well as a hasher
44+
// for computing its hash.
45+
//
46+
// init immediately reads header data from the input and stores it. This leaves
47+
// the Reader in a state that is ready to read content.
48+
func (r *Reader) init(input io.Reader) (err error) {
49+
r.decompressor, err = zlib.NewReader(input)
50+
if err != nil {
51+
// TODO: Make this error match the ZLibErr in formats/packfile/reader.go?
52+
return ErrZLib
53+
}
54+
55+
err = r.header.Read(r.decompressor)
56+
if err != nil {
57+
r.decompressor.Close()
58+
return
59+
}
60+
61+
r.h = core.NewHasher(r.header.t, r.header.size)
62+
r.r = io.TeeReader(r.decompressor, r.h) // All reads from the decompressor also write to the hash
63+
64+
return
65+
}
66+
67+
// Read reads len(p) bytes into p from the object data stream. It returns
68+
// the number of bytes read (0 <= n <= len(p)) and any error encountered. Even
69+
// if Read returns n < len(p), it may use all of p as scratch space during the
70+
// call.
71+
//
72+
// If Read encounters the end of the data stream it will return err == io.EOF,
73+
// either in the current call if n > 0 or in a subsequent call.
74+
func (r *Reader) Read(p []byte) (n int, err error) {
75+
if r.r == nil {
76+
return 0, ErrClosed
77+
}
78+
79+
return r.r.Read(p)
80+
}
81+
82+
// Type returns the type of the object.
83+
func (r *Reader) Type() core.ObjectType {
84+
return r.header.t
85+
}
86+
87+
// Size returns the uncompressed size of the object in bytes.
88+
func (r *Reader) Size() int64 {
89+
return r.header.size
90+
}
91+
92+
// Hash returns the hash of the object data stream that has been read so far.
93+
// It can be called before or after Close.
94+
func (r *Reader) Hash() core.Hash {
95+
if r.r != nil {
96+
return r.h.Sum() // Not yet closed, return hash of data read so far
97+
}
98+
return r.hash
99+
}
100+
101+
// Close releases any resources consumed by the Reader.
102+
//
103+
// Calling Close does not close the wrapped io.Reader originally passed to
104+
// NewReader.
105+
func (r *Reader) Close() (err error) {
106+
if r.r == nil {
107+
// TODO: Consider returning ErrClosed here?
108+
return nil // Already closed
109+
}
110+
111+
// Release the decompressor's resources
112+
err = r.decompressor.Close()
113+
114+
// Save the hash because we're about to throw away the hasher
115+
r.hash = r.h.Sum()
116+
117+
// Release references
118+
r.r = nil // Indicates closed state
119+
r.decompressor = nil
120+
r.h.Hash = nil
121+
122+
return
123+
}

0 commit comments

Comments
 (0)