aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commons/hash.go17
-rw-r--r--commons/hash_test.go12
-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.go237
-rw-r--r--formats/packfile/objects_test.go88
-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)29
-rw-r--r--formats/packfile/reader_test.go (renamed from packfile/reader_test.go)0
-rw-r--r--packfile/objects.go200
-rw-r--r--packfile/objects_test.go50
-rw-r--r--remote_test.go3
12 files changed, 349 insertions, 304 deletions
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/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..4c7ee75
--- /dev/null
+++ b/formats/packfile/objects.go
@@ -0,0 +1,237 @@
+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) {
+ // b64 := base64.StdEncoding.EncodeToString(b)
+ //fmt.Printf("%q\n", b64)
+
+ 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
+}
+
+type Tree struct {
+ Entries []TreeEntry
+ hash Hash
+}
+
+type TreeEntry struct {
+ Name string
+ Hash Hash
+}
+
+func NewTree(body []byte) (*Tree, error) {
+ o := &Tree{hash: ComputeHash(TreeObject, body)}
+
+ if len(body) == 0 {
+ return o, nil
+ }
+
+ for {
+ split := bytes.SplitN(body, []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)
+
+ body = split[1][20:]
+ if len(split[1]) == 20 {
+ break
+ }
+ }
+
+ return o, nil
+}
+
+func (o *Tree) Type() ObjectType {
+ return TreeObject
+}
+
+func (o *Tree) Hash() Hash {
+ return o.hash
+}
+
+type Blob struct {
+ Len int
+ hash Hash
+}
+
+func NewBlob(b []byte) (*Blob, error) {
+ return &Blob{
+ Len: len(b),
+ hash: ComputeHash(BlobObject, b),
+ }, nil
+}
+
+func (o *Blob) Type() ObjectType {
+ return BlobObject
+}
+
+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..70f4ae6
--- /dev/null
+++ b/formats/packfile/objects_test.go
@@ -0,0 +1,88 @@
+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")
+}
+
+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..ccf4822 100644
--- a/packfile/reader.go
+++ b/formats/packfile/reader.go
@@ -48,8 +48,8 @@ func (t *TrackingByteReader) ReadByte() (c byte, err error) {
type PackfileReader struct {
r *TrackingByteReader
- objects map[string]packfileObject
- offsets map[int]string
+ objects map[Hash]packfileObject
+ offsets map[int]Hash
deltas []packfileDelta
contentCallback ContentCallback
@@ -61,15 +61,15 @@ type packfileObject struct {
}
type packfileDelta struct {
- hash string
+ hash Hash
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),
+ objects: make(map[Hash]packfileObject, 0),
+ offsets: make(map[int]Hash, 0),
contentCallback: fn,
}, nil
}
@@ -196,7 +196,7 @@ const SIZE_LIMIT uint64 = 1 << 32 // 4GB
type objectReader struct {
pr *PackfileReader
pf *Packfile
- hash string
+ hash Hash
steps int
typ int8
@@ -230,7 +230,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,10 +240,9 @@ 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 {
@@ -292,14 +291,14 @@ 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)
+ return NewError("error while patching %q", ref)
}
o.typ = referenced.typ
err = o.addObject(patched)
@@ -321,11 +320,11 @@ 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)
+ c, err := ParseCommit(bytes)
if err != nil {
return err
}
diff --git a/packfile/reader_test.go b/formats/packfile/reader_test.go
index 04f2948..04f2948 100644
--- a/packfile/reader_test.go
+++ b/formats/packfile/reader_test.go
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..a3c602d 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{}