package git import ( "bufio" "bytes" "fmt" "io" "os" "strconv" "time" "gopkg.in/src-d/go-git.v2/internal" ) // 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 { Hash internal.Hash Tree internal.Hash Parents []internal.Hash Author Signature Committer Signature Message string } // Decode transform an internal.Object into a Blob struct func (c *Commit) Decode(o internal.Object) error { c.Hash = o.Hash() r := bufio.NewReader(o.Reader()) var message bool for { line, err := r.ReadSlice('\n') if err != nil && err != io.EOF { return err } line = bytes.TrimSpace(line) if !message { if len(line) == 0 { message = true continue } split := bytes.SplitN(line, []byte{' '}, 2) switch string(split[0]) { case "tree": c.Tree = internal.NewHash(string(split[1])) case "parent": c.Parents = append(c.Parents, internal.NewHash(string(split[1]))) case "author": c.Author = ParseSignature(split[1]) case "committer": c.Committer = ParseSignature(split[1]) } } else { c.Message += string(line) + "\n" } if err == io.EOF { return nil } } } // Tree is basically like a directory - it references a bunch of other trees // and/or blobs (i.e. files and sub-directories) type Tree struct { Entries []TreeEntry Hash internal.Hash } // TreeEntry represents a file type TreeEntry struct { Name string Mode os.FileMode Hash internal.Hash } // Decode transform an internal.Object into a Tree struct func (t *Tree) Decode(o internal.Object) error { t.Hash = o.Hash() if o.Size() == 0 { return nil } r := bufio.NewReader(o.Reader()) for { mode, err := r.ReadString(' ') if err != nil { if err == io.EOF { break } return err } fm, err := strconv.ParseInt(mode[:len(mode)-1], 8, 32) if err != nil && err != io.EOF { return err } name, err := r.ReadString(0) if err != nil && err != io.EOF { return err } var hash internal.Hash _, err = r.Read(hash[:]) if err != nil && err != io.EOF { return err } t.Entries = append(t.Entries, TreeEntry{ Hash: hash, Mode: os.FileMode(fm), Name: name[:len(name)-1], }) } return nil } // Blob is used to store file data - it is generally a file. type Blob struct { Hash internal.Hash Size int64 obj internal.Object } // Decode transform an internal.Object into a Blob struct func (b *Blob) Decode(o internal.Object) error { b.Hash = o.Hash() b.Size = o.Size() b.obj = o return nil } // Reader returns a reader allow the access to the content of the blob func (b *Blob) Reader() io.Reader { return b.obj.Reader() } // 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) }