diff options
Diffstat (limited to 'packfile/objects.go')
-rw-r--r-- | packfile/objects.go | 222 |
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) |