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)