aboutsummaryrefslogblamecommitdiffstats
path: root/plumbing/object/commit.go
blob: fff4f5363ca1df6b2299a19ac7117f129d77ff3e (plain) (tree)
1
2
3
4
5
6
7
8
9
              





               
              
                 
 


                                                  

 
                                        
                       
 





                                                                             





                                                                              
                           

                                                                  
 

                               





















                                                                                            

 
                                         
                                        
                                   

 
                                                     
                                        

                                                                                         
         

 




                                                        
                                                                      
                                                                       
                                            






                                                   

 









                                                             


                                                                               
                                                 
                                     


                     
                                                                            
  
                                                   

                                             

 

                                                                   
                                              


                                           
                         




                                 
                                             

                                    







                                                
                             
                                                    







                                                                   
                                                                           
                                      
                                                                                                 





                                                            
                                                 







                                  

                                                                           










                                                                

                                                            
                                        



                            
                                        

























                                                                                        

                                  
                                                        
                                                                 
                                                                    


         












                                                     
                                                        
                        

                                    

 


                                                                            
  
                                                                             

                                                                                             

 

                                                                             
                                                 
                                                 

                               

         
                                        

 
                                                                            
                                                                         
                                                                          
                                                               


                                                                                      


                                  
                            


          















                                                                  
                                                                       



                               
 
                                                                              



                                      
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 represents the 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 of the commit object.
	Hash plumbing.Hash
	// Author is the original author of the commit.
	Author Signature
	// Committer is the one performing the commit, might be different from
	// Author.
	Committer Signature
	// Message is the commit message, contains arbitrary text.
	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 returns a slice with the previous commits in the history of this
// commit, sorted in reverse chronological order.
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 takes a storer.EncodedObjectStorer and a
// storer.EncodedObjectIter and returns a *CommitIter that iterates over all
// commits contained in the storer.EncodedObjectIter.
//
// Any non-commit object returned by the storer.EncodedObjectIter is skipped.
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
// there are no more commits, it returns 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 sorts a commit list by commit date, from older to newer.
func SortCommits(l []*Commit) {
	s := &commitSorterer{l}
	sort.Sort(s)
}

// ReverseSortCommits sorts a commit list by commit date, from newer to older.
func ReverseSortCommits(l []*Commit) {
	s := &commitSorterer{l}
	sort.Sort(sort.Reverse(s))
}