diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-23 17:45:01 +0200 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-23 17:45:01 +0200 |
commit | d82f291cde9987322c8a0c81a325e1ba6159684c (patch) | |
tree | d423447ee374fbfa802f7ff354651fd34afe0fb2 /packfile | |
parent | 6c629843a1750a27c9af01ed2985f362f619c47a (diff) | |
parent | 27aa8cdd2431068606741a589383c02c149ea625 (diff) | |
download | go-git-d82f291cde9987322c8a0c81a325e1ba6159684c.tar.gz |
Merge pull request #2 from mcuadros/v2.0.0
formats/packfile: cleanup and hash type
Diffstat (limited to 'packfile')
-rw-r--r-- | packfile/delta.go | 93 | ||||
-rw-r--r-- | packfile/doc.go | 168 | ||||
-rw-r--r-- | packfile/objects.go | 200 | ||||
-rw-r--r-- | packfile/objects_test.go | 50 | ||||
-rw-r--r-- | packfile/packfile.go | 83 | ||||
-rw-r--r-- | packfile/reader.go | 402 | ||||
-rw-r--r-- | packfile/reader_test.go | 35 |
7 files changed, 0 insertions, 1031 deletions
diff --git a/packfile/delta.go b/packfile/delta.go deleted file mode 100644 index 86b556f..0000000 --- a/packfile/delta.go +++ /dev/null @@ -1,93 +0,0 @@ -package packfile - -const delta_size_min = 4 - -func deltaHeaderSize(b []byte) (uint, []byte) { - var size, j uint - var cmd byte - for { - cmd = b[j] - size |= (uint(cmd) & 0x7f) << (j * 7) - j++ - if uint(cmd)&0xb80 == 0 || j == uint(len(b)) { - break - } - } - return size, b[j:] -} - -func PatchDelta(src, delta []byte) []byte { - if len(delta) < delta_size_min { - return nil - } - size, delta := deltaHeaderSize(delta) - if size != uint(len(src)) { - return nil - } - size, delta = deltaHeaderSize(delta) - origSize := size - - dest := make([]byte, 0) - - // var offset uint - var cmd byte - for { - cmd = delta[0] - delta = delta[1:] - if (cmd & 0x80) != 0 { - var cp_off, cp_size uint - if (cmd & 0x01) != 0 { - cp_off = uint(delta[0]) - delta = delta[1:] - } - if (cmd & 0x02) != 0 { - cp_off |= uint(delta[0]) << 8 - delta = delta[1:] - } - if (cmd & 0x04) != 0 { - cp_off |= uint(delta[0]) << 16 - delta = delta[1:] - } - if (cmd & 0x08) != 0 { - cp_off |= uint(delta[0]) << 24 - delta = delta[1:] - } - - if (cmd & 0x10) != 0 { - cp_size = uint(delta[0]) - delta = delta[1:] - } - if (cmd & 0x20) != 0 { - cp_size |= uint(delta[0]) << 8 - delta = delta[1:] - } - if (cmd & 0x40) != 0 { - cp_size |= uint(delta[0]) << 16 - delta = delta[1:] - } - if cp_size == 0 { - cp_size = 0x10000 - } - if cp_off+cp_size < cp_off || - cp_off+cp_size > uint(len(src)) || - cp_size > origSize { - break - } - dest = append(dest, src[cp_off:cp_off+cp_size]...) - size -= cp_size - } else if cmd != 0 { - if uint(cmd) > origSize { - break - } - dest = append(dest, delta[0:uint(cmd)]...) - size -= uint(cmd) - delta = delta[uint(cmd):] - } else { - return nil - } - if size <= 0 { - break - } - } - return dest -} diff --git a/packfile/doc.go b/packfile/doc.go deleted file mode 100644 index 1fc28da..0000000 --- a/packfile/doc.go +++ /dev/null @@ -1,168 +0,0 @@ -package packfile - -// Code from: -// https://github.com/gitchain/gitchain/tree/master/git @ 4c2fabdf9 -// -// GIT pack format -// =============== -// -// == pack-*.pack files have the following format: -// -// - A header appears at the beginning and consists of the following: -// -// 4-byte signature: -// The signature is: {'P', 'A', 'C', 'K'} -// -// 4-byte version number (network byte order): -// GIT currently accepts version number 2 or 3 but -// generates version 2 only. -// -// 4-byte number of objects contained in the pack (network byte order) -// -// Observation: we cannot have more than 4G versions ;-) and -// more than 4G objects in a pack. -// -// - The header is followed by number of object entries, each of -// which looks like this: -// -// (undeltified representation) -// n-byte type and length (3-bit type, (n-1)*7+4-bit length) -// compressed data -// -// (deltified representation) -// n-byte type and length (3-bit type, (n-1)*7+4-bit length) -// 20-byte base object name -// compressed delta data -// -// Observation: length of each object is encoded in a variable -// length format and is not constrained to 32-bit or anything. -// -// - The trailer records 20-byte SHA1 checksum of all of the above. -// -// == Original (version 1) pack-*.idx files have the following format: -// -// - The header consists of 256 4-byte network byte order -// integers. N-th entry of this table records the number of -// objects in the corresponding pack, the first byte of whose -// object name is less than or equal to N. This is called the -// 'first-level fan-out' table. -// -// - The header is followed by sorted 24-byte entries, one entry -// per object in the pack. Each entry is: -// -// 4-byte network byte order integer, recording where the -// object is stored in the packfile as the offset from the -// beginning. -// -// 20-byte object name. -// -// - The file is concluded with a trailer: -// -// A copy of the 20-byte SHA1 checksum at the end of -// corresponding packfile. -// -// 20-byte SHA1-checksum of all of the above. -// -// Pack Idx file: -// -// -- +--------------------------------+ -// fanout | fanout[0] = 2 (for example) |-. -// table +--------------------------------+ | -// | fanout[1] | | -// +--------------------------------+ | -// | fanout[2] | | -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | -// | fanout[255] = total objects |---. -// -- +--------------------------------+ | | -// main | offset | | | -// index | object name 00XXXXXXXXXXXXXXXX | | | -// table +--------------------------------+ | | -// | offset | | | -// | object name 00XXXXXXXXXXXXXXXX | | | -// +--------------------------------+<+ | -// .-| offset | | -// | | object name 01XXXXXXXXXXXXXXXX | | -// | +--------------------------------+ | -// | | offset | | -// | | object name 01XXXXXXXXXXXXXXXX | | -// | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | -// | | offset | | -// | | object name FFXXXXXXXXXXXXXXXX | | -// --| +--------------------------------+<--+ -// trailer | | packfile checksum | -// | +--------------------------------+ -// | | idxfile checksum | -// | +--------------------------------+ -// .-------. -// | -// Pack file entry: <+ -// -// packed object header: -// 1-byte size extension bit (MSB) -// type (next 3 bit) -// size0 (lower 4-bit) -// n-byte sizeN (as long as MSB is set, each 7-bit) -// size0..sizeN form 4+7+7+..+7 bit integer, size0 -// is the least significant part, and sizeN is the -// most significant part. -// packed object data: -// If it is not DELTA, then deflated bytes (the size above -// is the size before compression). -// If it is REF_DELTA, then -// 20-byte base object name SHA1 (the size above is the -// size of the delta data that follows). -// delta data, deflated. -// If it is OFS_DELTA, then -// n-byte offset (see below) interpreted as a negative -// offset from the type-byte of the header of the -// ofs-delta entry (the size above is the size of -// the delta data that follows). -// delta data, deflated. -// -// offset encoding: -// n bytes with MSB set in all but the last one. -// The offset is then the number constructed by -// concatenating the lower 7 bit of each byte, and -// for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1)) -// to the result. -// -// -// -// == Version 2 pack-*.idx files support packs larger than 4 GiB, and -// have some other reorganizations. They have the format: -// -// - A 4-byte magic number '\377tOc' which is an unreasonable -// fanout[0] value. -// -// - A 4-byte version number (= 2) -// -// - A 256-entry fan-out table just like v1. -// -// - A table of sorted 20-byte SHA1 object names. These are -// packed together without offset values to reduce the cache -// footprint of the binary search for a specific object name. -// -// - A table of 4-byte CRC32 values of the packed object data. -// This is new in v2 so compressed data can be copied directly -// from pack to pack during repacking without undetected -// data corruption. -// -// - A table of 4-byte offset values (in network byte order). -// These are usually 31-bit pack file offsets, but large -// offsets are encoded as an index into the next table with -// the msbit set. -// -// - A table of 8-byte offset entries (empty for pack files less -// than 2 GiB). Pack files are organized with heavily used -// objects toward the front, so most object references should -// not need to refer to this table. -// -// - The same trailer as a v1 pack file: -// -// A copy of the 20-byte SHA1 checksum at the end of -// corresponding packfile. -// -// 20-byte SHA1-checksum of all of the above. -// -// From: -// https://www.kernel.org/pub/software/scm/git/docs/v1.7.5/technical/pack-protocol.txt diff --git a/packfile/objects.go b/packfile/objects.go deleted file mode 100644 index 6449808..0000000 --- a/packfile/objects.go +++ /dev/null @@ -1,200 +0,0 @@ -package packfile - -import ( - "bytes" - "encoding/hex" - "fmt" - "strconv" - "time" - - "gopkg.in/src-d/go-git.v2/commons" -) - -type Object interface { - Type() string - Hash() string -} - -type Hash []byte - -func (h Hash) String() string { - return hex.EncodeToString(h) -} - -type Commit struct { - Tree Hash - Parents []Hash - Author Signature - Committer Signature - Message string - hash string -} - -func NewCommit(b []byte) (*Commit, error) { - o := &Commit{hash: commons.GitHash("commit", b)} - - lines := bytes.Split(b, []byte{'\n'}) - for i := range lines { - if len(lines[i]) > 0 { - var err error - - split := bytes.SplitN(lines[i], []byte{' '}, 2) - switch string(split[0]) { - case "tree": - o.Tree = make([]byte, 20) - _, err = hex.Decode(o.Tree, split[1]) - case "parent": - h := make([]byte, 20) - _, err = hex.Decode(h, split[1]) - if err == nil { - o.Parents = append(o.Parents, h) - } - case "author": - o.Author = NewSignature(split[1]) - case "committer": - o.Committer = NewSignature(split[1]) - } - - if err != nil { - return nil, err - } - } else { - o.Message = string(bytes.Join(append(lines[i+1:]), []byte{'\n'})) - break - } - } - - return o, nil -} - -func (o *Commit) Type() string { - return "commit" -} - -func (o *Commit) Hash() string { - return o.hash -} - -type Signature struct { - Name string - Email string - When time.Time -} - -func NewSignature(signature []byte) Signature { - ret := Signature{} - if len(signature) == 0 { - return ret - } - - from := 0 - state := 'n' // n: name, e: email, t: timestamp, z: timezone - for i := 0; ; i++ { - var c byte - var end bool - if i < len(signature) { - c = signature[i] - } else { - end = true - } - - switch state { - case 'n': - if c == '<' || end { - if i == 0 { - break - } - ret.Name = string(signature[from : i-1]) - state = 'e' - from = i + 1 - } - case 'e': - if c == '>' || end { - ret.Email = string(signature[from:i]) - i++ - state = 't' - from = i + 1 - } - case 't': - if c == ' ' || end { - t, err := strconv.ParseInt(string(signature[from:i]), 10, 64) - if err == nil { - ret.When = time.Unix(t, 0) - } - end = true - } - } - - if end { - break - } - } - - return ret -} - -func (s *Signature) String() string { - return fmt.Sprintf("%q <%s> @ %s", s.Name, s.Email, s.When) -} - -type Tree struct { - Entries []TreeEntry - hash string -} - -type TreeEntry struct { - Name string - Hash string -} - -func NewTree(body []byte) (*Tree, error) { - o := &Tree{hash: commons.GitHash("tree", body)} - - if len(body) == 0 { - return o, nil - } - - for { - split := bytes.SplitN(body, []byte{0}, 2) - split1 := bytes.SplitN(split[0], []byte{' '}, 2) - - o.Entries = append(o.Entries, TreeEntry{ - Name: string(split1[1]), - Hash: fmt.Sprintf("%x", split[1][0:20]), - }) - - body = split[1][20:] - if len(split[1]) == 20 { - break - } - } - - return o, nil -} - -func (o *Tree) Type() string { - return "tree" -} - -func (o *Tree) Hash() string { - return o.hash -} - -type Blob struct { - Len int - hash string -} - -func NewBlob(b []byte) (*Blob, error) { - return &Blob{Len: len(b), hash: commons.GitHash("blob", b)}, nil -} - -func (o *Blob) Type() string { - return "blob" -} - -func (o *Blob) Hash() string { - return o.hash -} - -type ContentCallback func(hash string, content []byte) diff --git a/packfile/objects_test.go b/packfile/objects_test.go deleted file mode 100644 index 93c348b..0000000 --- a/packfile/objects_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package packfile - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestSignature(t *testing.T) { - cases := map[string]Signature{ - `Foo Bar <foo@bar.com> 1257894000 +0100`: { - Name: "Foo Bar", - Email: "foo@bar.com", - When: time.Unix(1257894000, 0), - }, - `Foo Bar <> 1257894000 +0100`: { - Name: "Foo Bar", - Email: "", - When: time.Unix(1257894000, 0), - }, - ` <> 1257894000`: { - Name: "", - Email: "", - When: time.Unix(1257894000, 0), - }, - `Foo Bar <foo@bar.com>`: { - Name: "Foo Bar", - Email: "foo@bar.com", - When: time.Time{}, - }, - ``: { - Name: "", - Email: "", - When: time.Time{}, - }, - `<`: { - Name: "", - Email: "", - When: time.Time{}, - }, - } - - for raw, exp := range cases { - got := NewSignature([]byte(raw)) - assert.Equal(t, exp.Name, got.Name) - assert.Equal(t, exp.Email, got.Email) - assert.Equal(t, exp.When.Unix(), got.When.Unix()) - } -} diff --git a/packfile/packfile.go b/packfile/packfile.go deleted file mode 100644 index 11ef969..0000000 --- a/packfile/packfile.go +++ /dev/null @@ -1,83 +0,0 @@ -package packfile - -import "fmt" - -type Packfile struct { - Version uint32 - Size int64 - ObjectCount int - Checksum []byte - Commits map[string]*Commit - Trees map[string]*Tree - Blobs map[string]*Blob -} - -func NewPackfile() *Packfile { - return &Packfile{ - Commits: make(map[string]*Commit, 0), - Trees: make(map[string]*Tree, 0), - Blobs: make(map[string]*Blob, 0), - } -} - -type BlobEntry struct { - path string - *Blob -} - -type SubtreeEntry struct { - path string - *Tree - TreeCh -} - -type treeEntry interface { - isTreeEntry() - Path() string -} - -func (b BlobEntry) isTreeEntry() {} -func (b BlobEntry) Path() string { return b.path } -func (b SubtreeEntry) isTreeEntry() {} -func (b SubtreeEntry) Path() string { return b.path } - -type TreeCh <-chan treeEntry - -func (p *Packfile) WalkCommit(commitHash string) (TreeCh, error) { - commit, ok := p.Commits[commitHash] - if !ok { - return nil, fmt.Errorf("Unable to find %q commit", commitHash) - } - - treeHash := fmt.Sprintf("%x", string(commit.Tree)) - return p.WalkTree(p.Trees[treeHash]), nil -} - -func (p *Packfile) WalkTree(tree *Tree) TreeCh { - return p.walkTree(tree, "") -} - -func (p *Packfile) walkTree(tree *Tree, pathPrefix string) TreeCh { - ch := make(chan treeEntry) - - if tree == nil { - close(ch) - return ch - } - - go func() { - defer func() { - close(ch) - }() - for _, e := range tree.Entries { - path := pathPrefix + e.Name - if blob, ok := p.Blobs[e.Hash]; ok { - ch <- BlobEntry{path, blob} - } else if subtree, ok := p.Trees[e.Hash]; ok { - ch <- SubtreeEntry{path, subtree, p.walkTree(subtree, path+"/")} - } - } - }() - - return ch -} diff --git a/packfile/reader.go b/packfile/reader.go deleted file mode 100644 index e761654..0000000 --- a/packfile/reader.go +++ /dev/null @@ -1,402 +0,0 @@ -package packfile - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - - "github.com/klauspost/compress/zlib" -) - -const MaxObjectsLimit = 1000000 - -var ErrMaxSize = fmt.Errorf("Max size exceeded for in-memory client") - -type TrackingByteReader struct { - r io.Reader - n, l int -} - -func (t *TrackingByteReader) Pos() int { return t.n } - -func (t *TrackingByteReader) Read(p []byte) (n int, err error) { - n, err = t.r.Read(p) - if err != nil { - return 0, err - } - t.n += n - if t.n >= t.l { - return n, ErrMaxSize - } - return n, err -} - -func (t *TrackingByteReader) ReadByte() (c byte, err error) { - var p [1]byte - n, err := t.r.Read(p[:]) - if err != nil { - return 0, err - } - if n > 1 { - return 0, fmt.Errorf("read %d bytes, should have read just 1", n) - } - t.n += n // n is 1 - return p[0], nil -} - -type PackfileReader struct { - r *TrackingByteReader - - objects map[string]packfileObject - offsets map[int]string - deltas []packfileDelta - - contentCallback ContentCallback -} - -type packfileObject struct { - bytes []byte - typ int8 -} - -type packfileDelta struct { - hash string - delta []byte -} - -func NewPackfileReader(r io.Reader, l int, fn ContentCallback) (*PackfileReader, error) { - return &PackfileReader{ - r: &TrackingByteReader{r: r, n: 0, l: l}, - objects: make(map[string]packfileObject, 0), - offsets: make(map[int]string, 0), - contentCallback: fn, - }, nil -} - -func (pr *PackfileReader) Pos() int { return pr.r.Pos() } - -func (pr *PackfileReader) Read() (*Packfile, error) { - packfile := NewPackfile() - - if err := pr.validateHeader(); err != nil { - if err == io.EOF { - // This is an empty repo. It's OK. - return packfile, nil - } - return nil, err - } - - ver, err := pr.readInt32() - if err != nil { - return nil, err - } - - count, err := pr.readInt32() - if err != nil { - return nil, err - } - - packfile.Version = uint32(ver) - packfile.ObjectCount = int(count) - - if packfile.ObjectCount > MaxObjectsLimit { - return nil, NewError("too many objects (%d)", packfile.ObjectCount) - } - - if err := pr.readObjects(packfile); err != nil { - return nil, err - } - - packfile.Size = int64(pr.r.Pos()) - - return packfile, nil -} - -func (pr *PackfileReader) validateHeader() error { - var header = make([]byte, 4) - if _, err := pr.r.Read(header); err != nil { - return err - } - - if !bytes.Equal(header, []byte{'P', 'A', 'C', 'K'}) { - return NewError("Pack file does not start with 'PACK'") - } - - return nil -} - -func (pr *PackfileReader) readInt32() (uint32, error) { - var value uint32 - if err := binary.Read(pr.r, binary.BigEndian, &value); err != nil { - return 0, err - } - - return value, nil -} - -func (pr *PackfileReader) readObjects(packfile *Packfile) error { - // This code has 50-80 µs of overhead per object not counting zlib inflation. - // Together with zlib inflation, it's 400-410 µs for small objects. - // That's 1 sec for ~2450 objects, ~4.20 MB, or ~250 ms per MB, - // of which 12-20 % is _not_ zlib inflation (ie. is our code). - - for i := 0; i < packfile.ObjectCount; i++ { - var pos = pr.Pos() - obj, err := pr.readObject(packfile) - if err != nil && err != io.EOF { - return err - } - - pr.offsets[pos] = obj.hash - - if err == io.EOF { - break - } - } - - return nil -} - -func (pr *PackfileReader) readObject(packfile *Packfile) (*objectReader, error) { - o, err := newObjectReader(pr, packfile) - if err != nil { - return nil, err - } - - switch o.typ { - case OBJ_REF_DELTA: - err = o.readREFDelta() - case OBJ_OFS_DELTA: - err = o.readOFSDelta() - case OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG: - err = o.readObject() - default: - err = NewError("Invalid git object tag %q", o.typ) - } - - if err != nil { - return nil, err - } - - return o, err -} - -const ( - OBJ_COMMIT = 1 - OBJ_TREE = 2 - OBJ_BLOB = 3 - OBJ_TAG = 4 - OBJ_OFS_DELTA = 6 - OBJ_REF_DELTA = 7 -) - -const SIZE_LIMIT uint64 = 1 << 32 // 4GB - -type objectReader struct { - pr *PackfileReader - pf *Packfile - hash string - steps int - - typ int8 - size uint64 -} - -func newObjectReader(pr *PackfileReader, pf *Packfile) (*objectReader, error) { - o := &objectReader{pr: pr, pf: pf} - - var buf [1]byte - if _, err := o.Read(buf[:]); err != nil { - return nil, err - } - - o.typ = int8((buf[0] >> 4) & 7) - o.size = uint64(buf[0] & 15) - o.steps++ // byte we just read to get `o.typ` and `o.size` - - var shift uint = 4 - for buf[0]&0x80 == 0x80 { - if _, err := o.Read(buf[:]); err != nil { - return nil, err - } - - o.size += uint64(buf[0]&0x7f) << shift - o.steps++ // byte we just read to update `o.size` - shift += 7 - } - - return o, nil -} - -func (o *objectReader) readREFDelta() error { - var ref [20]byte - if _, err := o.Read(ref[:]); err != nil { - return err - } - - buf, err := o.inflate() - if err != nil { - return err - } - - refhash := fmt.Sprintf("%x", ref) - referenced, ok := o.pr.objects[refhash] - if !ok { - o.pr.deltas = append(o.pr.deltas, packfileDelta{hash: refhash, delta: buf[:]}) - } else { - patched := PatchDelta(referenced.bytes, buf[:]) - if patched == nil { - return NewError("error while patching %x", ref) - } - o.typ = referenced.typ - err = o.addObject(patched) - if err != nil { - return err - } - } - - return nil -} - -func decodeOffset(src io.ByteReader, steps int) (int, error) { - b, err := src.ReadByte() - if err != nil { - return 0, err - } - var offset = int(b & 0x7f) - for (b & 0x80) != 0 { - offset += 1 // WHY? - b, err = src.ReadByte() - if err != nil { - return 0, err - } - offset = (offset << 7) + int(b&0x7f) - } - // offset needs to be aware of the bytes we read for `o.typ` and `o.size` - offset += steps - return -offset, nil -} - -func (o *objectReader) readOFSDelta() error { - var pos = o.pr.Pos() - - // read negative offset - offset, err := decodeOffset(o.pr.r, o.steps) - if err != nil { - return err - } - - buf, err := o.inflate() - if err != nil { - return err - } - - refhash := o.pr.offsets[pos+offset] - referenced, ok := o.pr.objects[refhash] - if !ok { - return NewError("can't find a pack entry at %d", pos+offset) - } else { - patched := PatchDelta(referenced.bytes, buf) - if patched == nil { - return NewError("error while patching %x", refhash) - } - o.typ = referenced.typ - err = o.addObject(patched) - if err != nil { - return err - } - } - - return nil -} - -func (o *objectReader) readObject() error { - buf, err := o.inflate() - if err != nil { - return err - } - - return o.addObject(buf) -} - -func (o *objectReader) addObject(bytes []byte) error { - var hash string - - switch o.typ { - case OBJ_COMMIT: - c, err := NewCommit(bytes) - if err != nil { - return err - } - o.pf.Commits[c.Hash()] = c - hash = c.Hash() - case OBJ_TREE: - c, err := NewTree(bytes) - if err != nil { - return err - } - o.pf.Trees[c.Hash()] = c - hash = c.Hash() - case OBJ_BLOB: - c, err := NewBlob(bytes) - if err != nil { - return err - } - o.pf.Blobs[c.Hash()] = c - hash = c.Hash() - - if o.pr.contentCallback != nil { - o.pr.contentCallback(hash, bytes) - } - } - - o.pr.objects[hash] = packfileObject{bytes: bytes, typ: o.typ} - o.hash = hash - - return nil -} - -func (o *objectReader) inflate() ([]byte, error) { - zr, err := zlib.NewReader(o.pr.r) - if err != nil { - if err == zlib.ErrHeader { - return nil, zlib.ErrHeader - } else { - return nil, NewError("error opening packfile's object zlib: %v", err) - } - } - defer zr.Close() - - if o.size > SIZE_LIMIT { - return nil, NewError("the object size exceeed the allowed limit: %d", o.size) - } - - var buf bytes.Buffer - io.Copy(&buf, zr) // also: io.CopyN(&buf, zr, int64(o.size)) - - var bufLen = buf.Len() - if bufLen != int(o.size) { - return nil, NewError("inflated size mismatch, expected %d, got %d", o.size, bufLen) - } - - return buf.Bytes(), nil -} - -func (o *objectReader) Read(p []byte) (int, error) { - return o.pr.r.Read(p) -} - -func (o *objectReader) ReadByte() (byte, error) { - return o.pr.r.ReadByte() -} - -type ReaderError struct { - Msg string // description of error -} - -func NewError(format string, args ...interface{}) error { - return &ReaderError{Msg: fmt.Sprintf(format, args...)} -} - -func (e *ReaderError) Error() string { return e.Msg } diff --git a/packfile/reader_test.go b/packfile/reader_test.go deleted file mode 100644 index 04f2948..0000000 --- a/packfile/reader_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package packfile - -import ( - "bytes" - "encoding/base64" - "testing" - - "github.com/stretchr/testify/assert" -) - -var packFileWithEmptyObjects = "UEFDSwAAAAIAAAALnw54nKXMQWoDMQxA0b1PoX2hSLIm44FSAlmXnEG2NYlhXAfHgdLb5Cy9WAM5Qpb/Lf7oZqArUpakyYtQjCoxZ5lmWXwwyuzJbHqAuYt2+x6QoyCyhYCKIa67lGameSLWvPh5JU0hsCg7vY1z6/D1d/8ptcHhprm3Kxz7KL/wUdOz96eqZXtPrX4CCeOOPU8Eb0iI7qG1jGGvXdxaNoPs/gHeNkp8lA94nKXMQUpDMRCA4X1OMXtBZpI3L3kiRXAtPcMkmWjgxZSYQultPEsv1oJHcPl/i38OVRC0IXF0lshrJorZEcpKmTEJYbA+B3aFzEmGfk9gpqJEsmnZNutXF71i1IURU/G0bsWWwJ6NnOdXH/Bx+73U1uH9LHn0HziOWa/w2tJfv302qftz6u0AtFh0wQdmeEJCNA9tdU7938WUuivEF5CczR11ZEsNnw54nKWMUQoCIRRF/13F+w/ijY6jQkTQd7SGpz5LyAxzINpNa2ljTbSEPu/hnNsbM4TJTzqyt561GdUUmJKT6K2MeiCVgnZWoY/iRo2vHVS0URrUS+e+dkqIEp11HMhh9IaUkRM6QXM/1waH9+uRS4X9TLHVOxxbz0/YlPDbu1OhfFmHWrYwjBKVNVaNsMIBUSy05N75vxeR8oXBiw8GoErCnwt4nKXMzQkCMRBA4XuqmLsgM2M2ZkAWwbNYQ341sCEQsyB2Yy02pmAJHt93eKOnBFpMNJqtl5CFxVIMomViomQSEWP2JrN3yq3j1jqc369HqQ1Oq4u93eHSR3nCoYZfH6/VlWUbWp2BNOPO7i1OsEFCVF+tZYz030XlsiRw6gPZ0jxaqwV4nDM0MDAzMVFIZHg299HsTRevOXt3a64rj7px6ElP8ERDiGQSQ2uoXe8RrcodS5on+J4/u8HjD4NDKFQyRS8tPx+rbgDt3yiEMHicAwAAAAABPnicS0wEAa4kMOACACTjBKdkZXici7aaYAUAA3gBYKoDeJwzNDAwMzFRSGR4NvfR7E0Xrzl7d2uuK4+6cehJT/BEQ4hkEsOELYFJvS2eX47UJdVttFQrenrmzQwA13MaiDd4nEtMBAEuAApMAlGtAXicMzQwMDMxUUhkeDb30exNF685e3drriuPunHoSU/wRACvkA258N/i8hVXx9CiAZzvFXNIhCuSFmE=" - -func TestReadPackfile(t *testing.T) { - data, _ := base64.StdEncoding.DecodeString(packFileWithEmptyObjects) - d := bytes.NewReader(data) - - r, err := NewPackfileReader(d, 8<<20, nil) - assert.Nil(t, err) - - p, err := r.Read() - assert.Nil(t, err) - - assert.Equal(t, 11, p.ObjectCount) - assert.Equal(t, 4, len(p.Commits)) - assert.Equal(t, 4, len(p.Trees)) -} - -func TestReadPackfileInvalid(t *testing.T) { - r, err := NewPackfileReader(bytes.NewReader([]byte("dasdsadasas")), 8<<20, nil) - assert.Nil(t, err) - - _, err = r.Read() - _, ok := err.(*ReaderError) - assert.True(t, ok) -} |