aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2015-10-23 17:45:01 +0200
committerMáximo Cuadros <mcuadros@gmail.com>2015-10-23 17:45:01 +0200
commitd82f291cde9987322c8a0c81a325e1ba6159684c (patch)
treed423447ee374fbfa802f7ff354651fd34afe0fb2
parent6c629843a1750a27c9af01ed2985f362f619c47a (diff)
parent27aa8cdd2431068606741a589383c02c149ea625 (diff)
downloadgo-git-d82f291cde9987322c8a0c81a325e1ba6159684c.tar.gz
Merge pull request #2 from mcuadros/v2.0.0
formats/packfile: cleanup and hash type
-rw-r--r--.travis.yml2
-rw-r--r--commons/hash.go17
-rw-r--r--commons/hash_test.go12
-rw-r--r--formats/packfile/common.go39
-rw-r--r--formats/packfile/common_test.go9
-rw-r--r--formats/packfile/delta.go (renamed from packfile/delta.go)0
-rw-r--r--formats/packfile/doc.go (renamed from packfile/doc.go)0
-rw-r--r--formats/packfile/objects.go244
-rw-r--r--formats/packfile/objects_test.go116
-rw-r--r--formats/packfile/packfile.go (renamed from packfile/packfile.go)17
-rw-r--r--formats/packfile/reader.go (renamed from packfile/reader.go)183
-rw-r--r--formats/packfile/reader_test.go (renamed from packfile/reader_test.go)4
-rw-r--r--packfile/objects.go200
-rw-r--r--packfile/objects_test.go50
-rw-r--r--remote_test.go5
15 files changed, 505 insertions, 393 deletions
diff --git a/.travis.yml b/.travis.yml
index 235ad5d..8bd2ecb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,7 @@ script:
- tail -n +2 tmp.out >> coverage.out
- go test -v gopkg.in/src-d/go-git.v2/clients/http -covermode=count -coverprofile=tmp.out
- tail -n +2 tmp.out >> coverage.out
- - go test -v gopkg.in/src-d/go-git.v2/packfile -covermode=count -coverprofile=tmp.out
+ - go test -v gopkg.in/src-d/go-git.v2/formats/packfile -covermode=count -coverprofile=tmp.out
- tail -n +2 tmp.out >> coverage.out
- go test -v gopkg.in/src-d/go-git.v2/formats/pktline -covermode=count -coverprofile=tmp.out
- tail -n +2 tmp.out >> coverage.out
diff --git a/commons/hash.go b/commons/hash.go
deleted file mode 100644
index 03fa53f..0000000
--- a/commons/hash.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package commons
-
-import (
- "crypto/sha1"
- "fmt"
- "strconv"
-)
-
-func GitHash(t string, b []byte) string {
- h := []byte(t)
- h = append(h, ' ')
- h = strconv.AppendInt(h, int64(len(b)), 10)
- h = append(h, 0)
- h = append(h, b...)
-
- return fmt.Sprintf("%x", sha1.Sum(h))
-}
diff --git a/commons/hash_test.go b/commons/hash_test.go
deleted file mode 100644
index 79a22ea..0000000
--- a/commons/hash_test.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package commons
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestCalculateHash(t *testing.T) {
- assert.Equal(t, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", GitHash("blob", []byte("")))
- assert.Equal(t, "8ab686eafeb1f44702738c8b0f24f2567c36da6d", GitHash("blob", []byte("Hello, World!\n")))
-}
diff --git a/formats/packfile/common.go b/formats/packfile/common.go
new file mode 100644
index 0000000..4a97dc7
--- /dev/null
+++ b/formats/packfile/common.go
@@ -0,0 +1,39 @@
+package packfile
+
+import (
+ "fmt"
+ "io"
+)
+
+type trackingReader struct {
+ r io.Reader
+ n int
+}
+
+func (t *trackingReader) Pos() int { return t.n }
+
+func (t *trackingReader) Read(p []byte) (n int, err error) {
+ n, err = t.r.Read(p)
+ if err != nil {
+ return 0, err
+ }
+
+ t.n += n
+
+ return n, err
+}
+
+func (t *trackingReader) 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
+}
diff --git a/formats/packfile/common_test.go b/formats/packfile/common_test.go
new file mode 100644
index 0000000..104a5d2
--- /dev/null
+++ b/formats/packfile/common_test.go
@@ -0,0 +1,9 @@
+package packfile
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
diff --git a/packfile/delta.go b/formats/packfile/delta.go
index 86b556f..86b556f 100644
--- a/packfile/delta.go
+++ b/formats/packfile/delta.go
diff --git a/packfile/doc.go b/formats/packfile/doc.go
index 1fc28da..1fc28da 100644
--- a/packfile/doc.go
+++ b/formats/packfile/doc.go
diff --git a/formats/packfile/objects.go b/formats/packfile/objects.go
new file mode 100644
index 0000000..1077b5f
--- /dev/null
+++ b/formats/packfile/objects.go
@@ -0,0 +1,244 @@
+package packfile
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "encoding/hex"
+ "fmt"
+ "strconv"
+ "time"
+)
+
+type ObjectType string
+
+const (
+ CommitObject ObjectType = "commit"
+ TreeObject ObjectType = "tree"
+ BlobObject ObjectType = "blob"
+)
+
+// Object generic object interface
+type Object interface {
+ Type() ObjectType
+ Hash() Hash
+}
+
+// Hash SHA1 hased content
+type Hash [20]byte
+
+// ComputeHash compute the hash for a given objType and content
+func ComputeHash(t ObjectType, content []byte) Hash {
+ h := []byte(t)
+ h = append(h, ' ')
+ h = strconv.AppendInt(h, int64(len(content)), 10)
+ h = append(h, 0)
+ h = append(h, content...)
+
+ return Hash(sha1.Sum(h))
+}
+
+func (h Hash) String() string {
+ return hex.EncodeToString(h[:])
+}
+
+// Commit points to a single tree, marking it as what the project looked like
+// at a certain point in time. It contains meta-information about that point
+// in time, such as a timestamp, the author of the changes since the last
+// commit, a pointer to the previous commit(s), etc.
+// http://schacon.github.io/gitbook/1_the_git_object_model.html
+type Commit struct {
+ Tree Hash
+ Parents []Hash
+ Author Signature
+ Committer Signature
+ Message string
+ hash Hash
+}
+
+// ParseCommit transform a byte slice into a Commit struct
+func ParseCommit(b []byte) (*Commit, error) {
+ o := &Commit{hash: ComputeHash(CommitObject, 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":
+ _, err = hex.Decode(o.Tree[:], split[1])
+ case "parent":
+ var h Hash
+ _, err = hex.Decode(h[:], split[1])
+ if err == nil {
+ o.Parents = append(o.Parents, h)
+ }
+ case "author":
+ o.Author = ParseSignature(split[1])
+ case "committer":
+ o.Committer = ParseSignature(split[1])
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ o.Message = string(bytes.Join(append(lines[i+1:]), []byte{'\n'}))
+ break
+ }
+
+ }
+
+ return o, nil
+}
+
+// Type returns the object type
+func (o *Commit) Type() ObjectType {
+ return CommitObject
+}
+
+// Hash returns the computed hash of the commit
+func (o *Commit) Hash() Hash {
+ return o.hash
+}
+
+// Tree is basically like a directory - it references a bunch of other trees
+// and/or blobs (i.e. files and sub-directories)
+type Tree struct {
+ Entries []TreeEntry
+ hash Hash
+}
+
+// TreeEntry represents a file
+type TreeEntry struct {
+ Name string
+ Hash Hash
+}
+
+// ParseTree transform a byte slice into a Tree struct
+func ParseTree(b []byte) (*Tree, error) {
+ o := &Tree{hash: ComputeHash(TreeObject, b)}
+
+ if len(b) == 0 {
+ return o, nil
+ }
+
+ for {
+ split := bytes.SplitN(b, []byte{0}, 2)
+ split1 := bytes.SplitN(split[0], []byte{' '}, 2)
+
+ entry := TreeEntry{}
+ entry.Name = string(split1[1])
+ copy(entry.Hash[:], split[1][0:20])
+
+ o.Entries = append(o.Entries, entry)
+
+ b = split[1][20:]
+ if len(split[1]) == 20 {
+ break
+ }
+ }
+
+ return o, nil
+}
+
+// Type returns the object type
+func (o *Tree) Type() ObjectType {
+ return TreeObject
+}
+
+// Hash returns the computed hash of the tree
+func (o *Tree) Hash() Hash {
+ return o.hash
+}
+
+// Blob is used to store file data - it is generally a file.
+type Blob struct {
+ Len int
+ hash Hash
+}
+
+// ParseBlob transform a byte slice into a Blob struct
+func ParseBlob(b []byte) (*Blob, error) {
+ return &Blob{
+ Len: len(b),
+ hash: ComputeHash(BlobObject, b),
+ }, nil
+}
+
+// Type returns the object type
+func (o *Blob) Type() ObjectType {
+ return BlobObject
+}
+
+// Hash returns the computed hash of the blob
+func (o *Blob) Hash() Hash {
+ return o.hash
+}
+
+type ContentCallback func(hash Hash, content []byte)
+
+// Signature represents an action signed by a person
+type Signature struct {
+ Name string
+ Email string
+ When time.Time
+}
+
+// ParseSignature parse a byte slice returning a new action signature.
+func ParseSignature(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)
+}
diff --git a/formats/packfile/objects_test.go b/formats/packfile/objects_test.go
new file mode 100644
index 0000000..5952432
--- /dev/null
+++ b/formats/packfile/objects_test.go
@@ -0,0 +1,116 @@
+package packfile
+
+import (
+ "encoding/base64"
+ "time"
+
+ . "gopkg.in/check.v1"
+)
+
+type ObjectsSuite struct{}
+
+var _ = Suite(&ObjectsSuite{})
+
+func (s *ObjectsSuite) TestComputeHash(c *C) {
+ hash := ComputeHash("blob", []byte(""))
+ c.Assert(hash.String(), Equals, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
+
+ hash = ComputeHash("blob", []byte("Hello, World!\n"))
+ c.Assert(hash.String(), Equals, "8ab686eafeb1f44702738c8b0f24f2567c36da6d")
+}
+
+var CommitFixture = "dHJlZSBjMmQzMGZhOGVmMjg4NjE4ZjY1ZjZlZWQ2ZTE2OGUwZDUxNDg4NmY0CnBhcmVudCBiMDI5NTE3ZjYzMDBjMmRhMGY0YjY1MWI4NjQyNTA2Y2Q2YWFmNDVkCnBhcmVudCBiOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3CmF1dGhvciBNw6F4aW1vIEN1YWRyb3MgPG1jdWFkcm9zQGdtYWlsLmNvbT4gMTQyNzgwMjQzNCArMDIwMApjb21taXR0ZXIgTcOheGltbyBDdWFkcm9zIDxtY3VhZHJvc0BnbWFpbC5jb20+IDE0Mjc4MDI0MzQgKzAyMDAKCk1lcmdlIHB1bGwgcmVxdWVzdCAjMSBmcm9tIGRyaXBvbGxlcy9mZWF0dXJlCgpDcmVhdGluZyBjaGFuZ2Vsb2c="
+
+func (s *ObjectsSuite) TestParseCommit(c *C) {
+ data, _ := base64.StdEncoding.DecodeString(CommitFixture)
+ commit, err := ParseCommit(data)
+ c.Assert(err, IsNil)
+
+ c.Assert(commit.Tree.String(), Equals, "c2d30fa8ef288618f65f6eed6e168e0d514886f4")
+ c.Assert(commit.Parents, HasLen, 2)
+ c.Assert(commit.Parents[0].String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")
+ c.Assert(commit.Parents[1].String(), Equals, "b8e471f58bcbca63b07bda20e428190409c2db47")
+ c.Assert(commit.Author.Email, Equals, "mcuadros@gmail.com")
+ c.Assert(commit.Author.Name, Equals, "Máximo Cuadros")
+ c.Assert(commit.Author.When.Unix(), Equals, int64(1427802434))
+ c.Assert(commit.Committer.Email, Equals, "mcuadros@gmail.com")
+ c.Assert(commit.Message, Equals, "Merge pull request #1 from dripolles/feature\n\nCreating changelog")
+}
+
+func (s *ObjectsSuite) TestCommitHash(c *C) {
+ data, _ := base64.StdEncoding.DecodeString(CommitFixture)
+ commit, err := ParseCommit(data)
+
+ c.Assert(err, IsNil)
+ c.Assert(commit.Hash().String(), Equals, "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69")
+}
+
+var TreeFixture = "MTAwNjQ0IC5naXRpZ25vcmUAMoWKrTw4PtH/Cg+b3yMdVKAMnogxMDA2NDQgQ0hBTkdFTE9HANP/U+BWSp+H2OhLbijlBg5RcAiqMTAwNjQ0IExJQ0VOU0UAwZK9aiTqGrAdeGhuQXyL3Hw9GX8xMDA2NDQgYmluYXJ5LmpwZwDVwPSrgRiXyt8DrsNYrmDSH5HFDTQwMDAwIGdvAKOXcadlH5f69ccuCCJNhX/DUTPbNDAwMDAganNvbgBah35qkGonQ61uRdmcF5NkKq+O2jQwMDAwIHBocABYavVn0Ltedx5JvdlDT14Pt20l+jQwMDAwIHZlbmRvcgDPSqOziXT7fYHzZ8CDD3141lq4aw=="
+
+func (s *ObjectsSuite) TestParseTree(c *C) {
+ data, _ := base64.StdEncoding.DecodeString(TreeFixture)
+ tree, err := ParseTree(data)
+ c.Assert(err, IsNil)
+
+ c.Assert(tree.Entries, HasLen, 8)
+ c.Assert(tree.Entries[0].Name, Equals, ".gitignore")
+ c.Assert(tree.Entries[0].Hash.String(), Equals, "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88")
+}
+
+func (s *ObjectsSuite) TestTreeHash(c *C) {
+ data, _ := base64.StdEncoding.DecodeString(TreeFixture)
+ tree, err := ParseTree(data)
+
+ c.Assert(err, IsNil)
+ c.Assert(tree.Hash().String(), Equals, "a8d315b2b1c615d43042c3a62402b8a54288cf5c")
+}
+
+func (s *ObjectsSuite) TestBlobHash(c *C) {
+ blob, err := ParseBlob([]byte{'F', 'O', 'O'})
+ c.Assert(err, IsNil)
+
+ c.Assert(blob.Len, Equals, 3)
+ c.Assert(blob.Hash().String(), Equals, "d96c7efbfec2814ae0301ad054dc8d9fc416c9b5")
+}
+
+func (s *ObjectsSuite) TestParseSignature(c *C) {
+ 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 := ParseSignature([]byte(raw))
+ c.Assert(got.Name, Equals, exp.Name)
+ c.Assert(got.Email, Equals, exp.Email)
+ c.Assert(got.When.Unix(), Equals, exp.When.Unix())
+ }
+}
diff --git a/packfile/packfile.go b/formats/packfile/packfile.go
index 11ef969..d70f396 100644
--- a/packfile/packfile.go
+++ b/formats/packfile/packfile.go
@@ -7,16 +7,16 @@ type Packfile struct {
Size int64
ObjectCount int
Checksum []byte
- Commits map[string]*Commit
- Trees map[string]*Tree
- Blobs map[string]*Blob
+ Commits map[Hash]*Commit
+ Trees map[Hash]*Tree
+ Blobs map[Hash]*Blob
}
func NewPackfile() *Packfile {
return &Packfile{
- Commits: make(map[string]*Commit, 0),
- Trees: make(map[string]*Tree, 0),
- Blobs: make(map[string]*Blob, 0),
+ Commits: make(map[Hash]*Commit, 0),
+ Trees: make(map[Hash]*Tree, 0),
+ Blobs: make(map[Hash]*Blob, 0),
}
}
@@ -43,14 +43,13 @@ func (b SubtreeEntry) Path() string { return b.path }
type TreeCh <-chan treeEntry
-func (p *Packfile) WalkCommit(commitHash string) (TreeCh, error) {
+func (p *Packfile) WalkCommit(commitHash Hash) (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
+ return p.WalkTree(p.Trees[commit.Tree]), nil
}
func (p *Packfile) WalkTree(tree *Tree) TreeCh {
diff --git a/packfile/reader.go b/formats/packfile/reader.go
index e761654..d5f40b9 100644
--- a/packfile/reader.go
+++ b/formats/packfile/reader.go
@@ -9,49 +9,34 @@ import (
"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
-}
+const (
+ DefaultMaxObjectsLimit = 1 << 20
+ DefaultMaxObjectSize = 1 << 32 // 4GB
+
+ rawCommit = 1
+ rawTree = 2
+ rawBlob = 3
+ rawTag = 4
+ rawOFSDelta = 6
+ rawREFDelta = 7
+)
type PackfileReader struct {
- r *TrackingByteReader
-
- objects map[string]packfileObject
- offsets map[int]string
- deltas []packfileDelta
-
+ // MaxObjectsLimit is the limit of objects to be load in the packfile, if
+ // a packfile excess this number an error is throw, the default value
+ // is defined by DefaultMaxObjectsLimit, usually the default limit is more
+ // than enough to work with any repository, working extremly big repositories
+ // where the number of object is bigger the memory can be exhausted.
+ MaxObjectsLimit int
+
+ // MaxObjectSize is the maximum size in bytes, reading objects with a bigger
+ // size cause a error. The default value is defined by DefaultMaxObjectSize
+ MaxObjectSize int
+
+ r *trackingReader
+ objects map[Hash]packfileObject
+ offsets map[int]Hash
+ deltas []packfileDelta
contentCallback ContentCallback
}
@@ -61,21 +46,21 @@ type packfileObject struct {
}
type packfileDelta struct {
- hash string
+ hash Hash
delta []byte
}
-func NewPackfileReader(r io.Reader, l int, fn ContentCallback) (*PackfileReader, error) {
+func NewPackfileReader(r io.Reader, 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),
+ MaxObjectsLimit: DefaultMaxObjectsLimit,
+ MaxObjectSize: DefaultMaxObjectSize,
+ r: &trackingReader{r: r},
+ objects: make(map[Hash]packfileObject, 0),
+ offsets: make(map[int]Hash, 0),
contentCallback: fn,
}, nil
}
-func (pr *PackfileReader) Pos() int { return pr.r.Pos() }
-
func (pr *PackfileReader) Read() (*Packfile, error) {
packfile := NewPackfile()
@@ -100,8 +85,9 @@ func (pr *PackfileReader) Read() (*Packfile, error) {
packfile.Version = uint32(ver)
packfile.ObjectCount = int(count)
- if packfile.ObjectCount > MaxObjectsLimit {
- return nil, NewError("too many objects (%d)", packfile.ObjectCount)
+ if packfile.ObjectCount > pr.MaxObjectsLimit {
+ return nil, NewError("too many objects %d, limit is %d",
+ packfile.ObjectCount, pr.MaxObjectsLimit)
}
if err := pr.readObjects(packfile); err != nil {
@@ -159,17 +145,17 @@ func (pr *PackfileReader) readObjects(packfile *Packfile) error {
}
func (pr *PackfileReader) readObject(packfile *Packfile) (*objectReader, error) {
- o, err := newObjectReader(pr, packfile)
+ o, err := newObjectReader(pr, packfile, pr.MaxObjectSize)
if err != nil {
return nil, err
}
switch o.typ {
- case OBJ_REF_DELTA:
+ case rawREFDelta:
err = o.readREFDelta()
- case OBJ_OFS_DELTA:
+ case rawOFSDelta:
err = o.readOFSDelta()
- case OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG:
+ case rawCommit, rawTree, rawBlob, rawTag:
err = o.readObject()
default:
err = NewError("Invalid git object tag %q", o.typ)
@@ -182,29 +168,21 @@ func (pr *PackfileReader) readObject(packfile *Packfile) (*objectReader, error)
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
+func (pr *PackfileReader) Pos() int { return pr.r.Pos() }
type objectReader struct {
- pr *PackfileReader
- pf *Packfile
- hash string
- steps int
+ pr *PackfileReader
+ pf *Packfile
+ maxSize uint64
- typ int8
- size uint64
+ hash Hash
+ steps int
+ typ int8
+ size uint64
}
-func newObjectReader(pr *PackfileReader, pf *Packfile) (*objectReader, error) {
- o := &objectReader{pr: pr, pf: pf}
+func newObjectReader(pr *PackfileReader, pf *Packfile, maxSize int) (*objectReader, error) {
+ o := &objectReader{pr: pr, pf: pf, maxSize: uint64(maxSize)}
var buf [1]byte
if _, err := o.Read(buf[:]); err != nil {
@@ -230,7 +208,7 @@ func newObjectReader(pr *PackfileReader, pf *Packfile) (*objectReader, error) {
}
func (o *objectReader) readREFDelta() error {
- var ref [20]byte
+ var ref Hash
if _, err := o.Read(ref[:]); err != nil {
return err
}
@@ -240,15 +218,15 @@ func (o *objectReader) readREFDelta() error {
return err
}
- refhash := fmt.Sprintf("%x", ref)
- referenced, ok := o.pr.objects[refhash]
+ referenced, ok := o.pr.objects[ref]
if !ok {
- o.pr.deltas = append(o.pr.deltas, packfileDelta{hash: refhash, delta: buf[:]})
+ o.pr.deltas = append(o.pr.deltas, packfileDelta{hash: ref, 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 {
@@ -266,13 +244,15 @@ func decodeOffset(src io.ByteReader, steps int) (int, error) {
}
var offset = int(b & 0x7f)
for (b & 0x80) != 0 {
- offset += 1 // WHY?
+ offset++ // 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
@@ -292,20 +272,21 @@ func (o *objectReader) readOFSDelta() error {
return err
}
- refhash := o.pr.offsets[pos+offset]
- referenced, ok := o.pr.objects[refhash]
+ ref := o.pr.offsets[pos+offset]
+ referenced, ok := o.pr.objects[ref]
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
- }
+ }
+
+ patched := PatchDelta(referenced.bytes, buf)
+ if patched == nil {
+ return NewError("error while patching %q", ref)
+ }
+
+ o.typ = referenced.typ
+ err = o.addObject(patched)
+ if err != nil {
+ return err
}
return nil
@@ -321,25 +302,25 @@ func (o *objectReader) readObject() error {
}
func (o *objectReader) addObject(bytes []byte) error {
- var hash string
+ var hash Hash
switch o.typ {
- case OBJ_COMMIT:
- c, err := NewCommit(bytes)
+ case rawCommit:
+ c, err := ParseCommit(bytes)
if err != nil {
return err
}
o.pf.Commits[c.Hash()] = c
hash = c.Hash()
- case OBJ_TREE:
- c, err := NewTree(bytes)
+ case rawTree:
+ c, err := ParseTree(bytes)
if err != nil {
return err
}
o.pf.Trees[c.Hash()] = c
hash = c.Hash()
- case OBJ_BLOB:
- c, err := NewBlob(bytes)
+ case rawBlob:
+ c, err := ParseBlob(bytes)
if err != nil {
return err
}
@@ -362,14 +343,16 @@ func (o *objectReader) inflate() ([]byte, error) {
if err != nil {
if err == zlib.ErrHeader {
return nil, zlib.ErrHeader
- } else {
- return nil, NewError("error opening packfile's object zlib: %v", err)
}
+
+ 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)
+ if o.size > o.maxSize {
+ return nil, NewError("the object size %q exceeed the allowed limit: %q",
+ o.size, o.maxSize)
}
var buf bytes.Buffer
diff --git a/packfile/reader_test.go b/formats/packfile/reader_test.go
index 04f2948..e52cbc3 100644
--- a/packfile/reader_test.go
+++ b/formats/packfile/reader_test.go
@@ -14,7 +14,7 @@ func TestReadPackfile(t *testing.T) {
data, _ := base64.StdEncoding.DecodeString(packFileWithEmptyObjects)
d := bytes.NewReader(data)
- r, err := NewPackfileReader(d, 8<<20, nil)
+ r, err := NewPackfileReader(d, nil)
assert.Nil(t, err)
p, err := r.Read()
@@ -26,7 +26,7 @@ func TestReadPackfile(t *testing.T) {
}
func TestReadPackfileInvalid(t *testing.T) {
- r, err := NewPackfileReader(bytes.NewReader([]byte("dasdsadasas")), 8<<20, nil)
+ r, err := NewPackfileReader(bytes.NewReader([]byte("dasdsadasas")), nil)
assert.Nil(t, err)
_, err = r.Read()
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/remote_test.go b/remote_test.go
index 3426fce..881f629 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -1,8 +1,9 @@
package git
import (
+ "gopkg.in/src-d/go-git.v2/formats/packfile"
+
. "gopkg.in/check.v1"
- "gopkg.in/src-d/go-git.v2/packfile"
)
type SuiteRemote struct{}
@@ -39,7 +40,7 @@ func (s *SuiteRemote) TestFetchDefaultBranch(c *C) {
reader, err := r.FetchDefaultBranch()
c.Assert(err, IsNil)
- pr, err := packfile.NewPackfileReader(reader, 8<<20, nil)
+ pr, err := packfile.NewPackfileReader(reader, nil)
c.Assert(err, IsNil)
pf, err := pr.Read()