diff options
Diffstat (limited to 'plumbing/object/commit.go')
-rw-r--r-- | plumbing/object/commit.go | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go new file mode 100644 index 0000000..f0ce6e8 --- /dev/null +++ b/plumbing/object/commit.go @@ -0,0 +1,293 @@ +package object + +import ( + "bufio" + "bytes" + "fmt" + "io" + "sort" + "strings" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" + "gopkg.in/src-d/go-git.v4/utils/ioutil" +) + +// Hash hash of an object +type Hash plumbing.Hash + +// 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 plumbing.Hash + Author Signature + Committer Signature + Message string + + tree plumbing.Hash + parents []plumbing.Hash + s storer.EncodedObjectStorer +} + +// GetCommit gets a commit from an object storer and decodes it. +func GetCommit(s storer.EncodedObjectStorer, h plumbing.Hash) (*Commit, error) { + o, err := s.EncodedObject(plumbing.CommitObject, h) + if err != nil { + return nil, err + } + + return DecodeCommit(s, o) +} + +// DecodeCommit decodes an encoded object into a *Commit and associates it to +// the given object storer. +func DecodeCommit(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Commit, error) { + c := &Commit{s: s} + if err := c.Decode(o); err != nil { + return nil, err + } + + return c, nil +} + +// Tree returns the Tree from the commit +func (c *Commit) Tree() (*Tree, error) { + return GetTree(c.s, c.tree) +} + +// Parents return a CommitIter to the parent Commits +func (c *Commit) Parents() *CommitIter { + return NewCommitIter(c.s, + storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.parents), + ) +} + +// NumParents returns the number of parents in a commit. +func (c *Commit) NumParents() int { + return len(c.parents) +} + +// File returns the file with the specified "path" in the commit and a +// nil error if the file exists. If the file does not exist, it returns +// a nil file and the ErrFileNotFound error. +func (c *Commit) File(path string) (*File, error) { + tree, err := c.Tree() + if err != nil { + return nil, err + } + + return tree.File(path) +} + +// Files returns a FileIter allowing to iterate over the Tree +func (c *Commit) Files() (*FileIter, error) { + tree, err := c.Tree() + if err != nil { + return nil, err + } + + return tree.Files(), nil +} + +// ID returns the object ID of the commit. The returned value will always match +// the current value of Commit.Hash. +// +// ID is present to fulfill the Object interface. +func (c *Commit) ID() plumbing.Hash { + return c.Hash +} + +// Type returns the type of object. It always returns plumbing.CommitObject. +// +// Type is present to fulfill the Object interface. +func (c *Commit) Type() plumbing.ObjectType { + return plumbing.CommitObject +} + +// Decode transforms a plumbing.EncodedObject into a Commit struct. +func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { + if o.Type() != plumbing.CommitObject { + return ErrUnsupportedObject + } + + c.Hash = o.Hash() + + reader, err := o.Reader() + if err != nil { + return err + } + defer ioutil.CheckClose(reader, &err) + + r := bufio.NewReader(reader) + + var message bool + for { + line, err := r.ReadSlice('\n') + if err != nil && err != io.EOF { + return err + } + + if !message { + line = bytes.TrimSpace(line) + if len(line) == 0 { + message = true + continue + } + + split := bytes.SplitN(line, []byte{' '}, 2) + switch string(split[0]) { + case "tree": + c.tree = plumbing.NewHash(string(split[1])) + case "parent": + c.parents = append(c.parents, plumbing.NewHash(string(split[1]))) + case "author": + c.Author.Decode(split[1]) + case "committer": + c.Committer.Decode(split[1]) + } + } else { + c.Message += string(line) + } + + if err == io.EOF { + return nil + } + } +} + +// History return a slice with the previous commits in the history of this commit +func (c *Commit) History() ([]*Commit, error) { + var commits []*Commit + err := WalkCommitHistory(c, func(commit *Commit) error { + commits = append(commits, commit) + return nil + }) + + ReverseSortCommits(commits) + return commits, err +} + +// Encode transforms a Commit into a plumbing.EncodedObject. +func (b *Commit) Encode(o plumbing.EncodedObject) error { + o.SetType(plumbing.CommitObject) + w, err := o.Writer() + if err != nil { + return err + } + defer ioutil.CheckClose(w, &err) + if _, err = fmt.Fprintf(w, "tree %s\n", b.tree.String()); err != nil { + return err + } + for _, parent := range b.parents { + if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil { + return err + } + } + if _, err = fmt.Fprint(w, "author "); err != nil { + return err + } + if err = b.Author.Encode(w); err != nil { + return err + } + if _, err = fmt.Fprint(w, "\ncommitter "); err != nil { + return err + } + if err = b.Committer.Encode(w); err != nil { + return err + } + if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil { + return err + } + return err +} + +func (c *Commit) String() string { + return fmt.Sprintf( + "%s %s\nAuthor: %s\nDate: %s\n\n%s\n", + plumbing.CommitObject, c.Hash, c.Author.String(), + c.Author.When.Format(DateFormat), indent(c.Message), + ) +} + +func indent(t string) string { + var output []string + for _, line := range strings.Split(t, "\n") { + if len(line) != 0 { + line = " " + line + } + + output = append(output, line) + } + + return strings.Join(output, "\n") +} + +// CommitIter provides an iterator for a set of commits. +type CommitIter struct { + storer.EncodedObjectIter + s storer.EncodedObjectStorer +} + +// NewCommitIter returns a CommitIter for the given object storer and underlying +// object iterator. +// +// The returned CommitIter will automatically skip over non-commit objects. +func NewCommitIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *CommitIter { + return &CommitIter{iter, s} +} + +// Next moves the iterator to the next commit and returns a pointer to it. If it +// has reached the end of the set it will return io.EOF. +func (iter *CommitIter) Next() (*Commit, error) { + obj, err := iter.EncodedObjectIter.Next() + if err != nil { + return nil, err + } + + return DecodeCommit(iter.s, obj) +} + +// ForEach call the cb function for each commit contained on this iter until +// an error appends or the end of the iter is reached. If ErrStop is sent +// the iteration is stop but no error is returned. The iterator is closed. +func (iter *CommitIter) ForEach(cb func(*Commit) error) error { + return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { + c, err := DecodeCommit(iter.s, obj) + if err != nil { + return err + } + + return cb(c) + }) +} + +type commitSorterer struct { + l []*Commit +} + +func (s commitSorterer) Len() int { + return len(s.l) +} + +func (s commitSorterer) Less(i, j int) bool { + return s.l[i].Committer.When.Before(s.l[j].Committer.When) +} + +func (s commitSorterer) Swap(i, j int) { + s.l[i], s.l[j] = s.l[j], s.l[i] +} + +// SortCommits sort a commit list by commit date, from older to newer. +func SortCommits(l []*Commit) { + s := &commitSorterer{l} + sort.Sort(s) +} + +// ReverseSortCommits sort a commit list by commit date, from newer to older. +func ReverseSortCommits(l []*Commit) { + s := &commitSorterer{l} + sort.Sort(sort.Reverse(s)) +} |