aboutsummaryrefslogtreecommitdiffstats
path: root/packfile/objects.go
diff options
context:
space:
mode:
Diffstat (limited to 'packfile/objects.go')
-rw-r--r--packfile/objects.go222
1 files changed, 222 insertions, 0 deletions
diff --git a/packfile/objects.go b/packfile/objects.go
new file mode 100644
index 0000000..3c77a33
--- /dev/null
+++ b/packfile/objects.go
@@ -0,0 +1,222 @@
+package packfile
+
+import (
+ "bytes"
+ "compress/zlib"
+ "crypto/sha1"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "strconv"
+ "time"
+)
+
+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: calculateHash("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(b []byte) (*Tree, error) {
+ o := &Tree{hash: calculateHash("tree", b)}
+
+ if len(b) == 0 {
+ return o, nil
+ }
+
+ zr, e := zlib.NewReader(bytes.NewBuffer(b))
+ if e == nil {
+ defer zr.Close()
+ var err error
+ b, err = ioutil.ReadAll(zr)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ body := b
+ 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: calculateHash("blob", b)}, nil
+}
+
+func (o *Blob) Type() string {
+ return "blob"
+}
+
+func (o *Blob) Hash() string {
+ return o.hash
+}
+
+func calculateHash(objType string, content []byte) string {
+ header := []byte(objType)
+ header = append(header, ' ')
+ header = strconv.AppendInt(header, int64(len(content)), 10)
+ header = append(header, 0)
+ header = append(header, content...)
+
+ return fmt.Sprintf("%x", sha1.Sum(header))
+}
+
+type ContentCallback func(hash string, content []byte)