aboutsummaryrefslogtreecommitdiffstats
path: root/formats/objfile/reader.go
diff options
context:
space:
mode:
authorJoshua Sjoding <joshua.sjoding@scjalliance.com>2016-02-27 00:30:35 -0800
committerJoshua Sjoding <joshua.sjoding@scjalliance.com>2016-02-27 00:30:35 -0800
commite3cb5921c8f3b730a8bbd21877176197c20b8fc7 (patch)
treeb56c8d3d3ec2aa5a90d175da7010fe11ee2cdc3e /formats/objfile/reader.go
parentff809118743100300c38d0c626ffe8c840fb1275 (diff)
downloadgo-git-e3cb5921c8f3b730a8bbd21877176197c20b8fc7.tar.gz
Added objfile format used for loose git objects
Diffstat (limited to 'formats/objfile/reader.go')
-rw-r--r--formats/objfile/reader.go123
1 files changed, 123 insertions, 0 deletions
diff --git a/formats/objfile/reader.go b/formats/objfile/reader.go
new file mode 100644
index 0000000..b3c2e5c
--- /dev/null
+++ b/formats/objfile/reader.go
@@ -0,0 +1,123 @@
+package objfile
+
+import (
+ "errors"
+ "io"
+
+ "gopkg.in/src-d/go-git.v3/core"
+
+ "github.com/klauspost/compress/zlib"
+)
+
+var (
+ // ErrZLib is returned when the objfile contains invalid zlib data.
+ ErrZLib = errors.New("objfile: invalid zlib data")
+)
+
+// Reader reads and decodes compressed objfile data from a provided io.Reader.
+//
+// Reader implements io.ReadCloser. Close should be called when finished with
+// the Reader. Close will not close the underlying io.Reader.
+type Reader struct {
+ header header
+ hash core.Hash // final computed hash stored after Close
+
+ r io.Reader // provided reader wrapped in decompressor and tee
+ decompressor io.ReadCloser // provided reader wrapped in decompressor, retained for calling Close
+ h core.Hasher // streaming SHA1 hash of decoded data
+}
+
+// NewReader returns a new Reader reading from r.
+//
+// Calling NewReader causes it to immediately read in header data from r
+// containing size and type information. Any errors encountered in that
+// process will be returned in err.
+//
+// The returned Reader implements io.ReadCloser. Close should be called when
+// finished with the Reader. Close will not close the underlying io.Reader.
+func NewReader(r io.Reader) (*Reader, error) {
+ reader := &Reader{}
+ return reader, reader.init(r)
+}
+
+// init prepares the zlib decompressor for the given input as well as a hasher
+// for computing its hash.
+//
+// init immediately reads header data from the input and stores it. This leaves
+// the Reader in a state that is ready to read content.
+func (r *Reader) init(input io.Reader) (err error) {
+ r.decompressor, err = zlib.NewReader(input)
+ if err != nil {
+ // TODO: Make this error match the ZLibErr in formats/packfile/reader.go?
+ return ErrZLib
+ }
+
+ err = r.header.Read(r.decompressor)
+ if err != nil {
+ r.decompressor.Close()
+ return
+ }
+
+ r.h = core.NewHasher(r.header.t, r.header.size)
+ r.r = io.TeeReader(r.decompressor, r.h) // All reads from the decompressor also write to the hash
+
+ return
+}
+
+// Read reads len(p) bytes into p from the object data stream. It returns
+// the number of bytes read (0 <= n <= len(p)) and any error encountered. Even
+// if Read returns n < len(p), it may use all of p as scratch space during the
+// call.
+//
+// If Read encounters the end of the data stream it will return err == io.EOF,
+// either in the current call if n > 0 or in a subsequent call.
+func (r *Reader) Read(p []byte) (n int, err error) {
+ if r.r == nil {
+ return 0, ErrClosed
+ }
+
+ return r.r.Read(p)
+}
+
+// Type returns the type of the object.
+func (r *Reader) Type() core.ObjectType {
+ return r.header.t
+}
+
+// Size returns the uncompressed size of the object in bytes.
+func (r *Reader) Size() int64 {
+ return r.header.size
+}
+
+// Hash returns the hash of the object data stream that has been read so far.
+// It can be called before or after Close.
+func (r *Reader) Hash() core.Hash {
+ if r.r != nil {
+ return r.h.Sum() // Not yet closed, return hash of data read so far
+ }
+ return r.hash
+}
+
+// Close releases any resources consumed by the Reader.
+//
+// Calling Close does not close the wrapped io.Reader originally passed to
+// NewReader.
+func (r *Reader) Close() (err error) {
+ if r.r == nil {
+ // TODO: Consider returning ErrClosed here?
+ return nil // Already closed
+ }
+
+ // Release the decompressor's resources
+ err = r.decompressor.Close()
+
+ // Save the hash because we're about to throw away the hasher
+ r.hash = r.h.Sum()
+
+ // Release references
+ r.r = nil // Indicates closed state
+ r.decompressor = nil
+ r.h.Hash = nil
+
+ return
+}