git/odb: introduce *odb.Blob type

This commit is contained in:
Taylor Blau 2017-05-15 15:57:46 -06:00
parent f498b7b180
commit bcc36ba72f
2 changed files with 138 additions and 0 deletions

64
git/odb/blob.go Normal file

@ -0,0 +1,64 @@
package odb
import "io"
// Blob represents a Git object of type "blob".
type Blob struct {
// Size is the total uncompressed size of the blob's contents.
Size int64
// Contents is a reader that yields the uncompressed blob contents. It
// may only be read once. It may or may not implement io.ReadSeeker.
Contents io.Reader
// closeFn is a function that is called to free any resources held by
// the Blob. In particular, this will close a file, if the Blob is
// being read from a file on disk.
closeFn func() error
}
var _ Object = (*Blob)(nil)
// Type implements Object.ObjectType by returning the correct object type for
// Blobs, BlobObjectType.
func (b *Blob) Type() ObjectType { return BlobObjectType }
// Decode implements Object.Decode and decodes the uncompressed blob contents
// being read. It returns the number of bytes that it consumed off of the
// stream, which is always zero.
//
// If any error(s) was(were) encountered while reading the blob, that error will
// be returned.
func (b *Blob) Decode(r io.Reader, size int64) (n int, err error) {
b.Size = size
b.Contents = io.LimitReader(r, size)
b.closeFn = func() error {
if closer, ok := r.(io.Closer); ok {
return closer.Close()
}
return nil
}
return 0, nil
}
// Encode encodes the blob's contents to the given io.Writer, "w". If there was
// any error copying the blob's contents, that error will be returned.
//
// Otherwise, the number of bytes written will be returned.
func (b *Blob) Encode(to io.Writer) (n int, err error) {
nn, err := io.Copy(to, b.Contents)
if err != nil {
return 0, err
}
return int(nn), err
}
// Closes closes any resources held by the open Blob, or returns nil if there
// were no errors.
func (b *Blob) Close() error {
if b.closeFn == nil {
return nil
}
return b.closeFn()
}

74
git/odb/blob_test.go Normal file

@ -0,0 +1,74 @@
package odb
import (
"bytes"
"errors"
"io/ioutil"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBlobReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, BlobObjectType, new(Blob).Type())
}
func TestBlobEncoding(t *testing.T) {
const contents = "Hello, world!\n"
b := &Blob{
Size: int64(len(contents)),
Contents: strings.NewReader(contents),
}
var buf bytes.Buffer
if _, err := b.Encode(&buf); err != nil {
t.Fatal(err.Error())
}
assert.Equal(t, contents, (&buf).String())
}
func TestBlobDecoding(t *testing.T) {
const contents = "Hello, world!\n"
from := strings.NewReader(contents)
b := new(Blob)
n, err := b.Decode(from, int64(len(contents)))
assert.Equal(t, 0, n)
assert.Nil(t, err)
assert.EqualValues(t, len(contents), b.Size)
got, err := ioutil.ReadAll(b.Contents)
assert.Nil(t, err)
assert.Equal(t, []byte(contents), got)
}
func TestBlobCallCloseFn(t *testing.T) {
var calls uint32
expected := errors.New("some close error")
b := &Blob{
closeFn: func() error {
atomic.AddUint32(&calls, 1)
return expected
},
}
got := b.Close()
assert.Equal(t, expected, got)
assert.EqualValues(t, 1, calls)
}
func TestBlobCanCloseWithoutCloseFn(t *testing.T) {
b := &Blob{
closeFn: nil,
}
assert.Nil(t, b.Close())
}