From d0a18ccd8eea3bdabc76d6dc5420af1ea30aae9f Mon Sep 17 00:00:00 2001 From: Máximo Cuadros Date: Fri, 23 Oct 2015 14:28:49 +0200 Subject: formats/packfile: type Hash instead of strings --- formats/packfile/objects.go | 237 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 formats/packfile/objects.go (limited to 'formats/packfile/objects.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) +} -- cgit