github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/commit.go (about)

     1  package odb
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  // Signature represents a commit signature, which can represent either
    14  // committership or authorship of the commit that this signature belongs to. It
    15  // specifies a name, email, and time that the signature was created.
    16  //
    17  // NOTE: this type is _not_ used by the `*Commit` instance, as it does not
    18  // preserve cruft bytes. It is kept as a convenience type to test with.
    19  type Signature struct {
    20  	// Name is the first and last name of the individual holding this
    21  	// signature.
    22  	Name string
    23  	// Email is the email address of the individual holding this signature.
    24  	Email string
    25  	// When is the instant in time when the signature was created.
    26  	When time.Time
    27  }
    28  
    29  const (
    30  	formatTimeZoneOnly = "-0700"
    31  )
    32  
    33  // String implements the fmt.Stringer interface and formats a Signature as
    34  // expected in the Git commit internal object format. For instance:
    35  //
    36  //  Taylor Blau <ttaylorr@github.com> 1494258422 -0600
    37  func (s *Signature) String() string {
    38  	at := s.When.Unix()
    39  	zone := s.When.Format(formatTimeZoneOnly)
    40  
    41  	return fmt.Sprintf("%s <%s> %d %s", s.Name, s.Email, at, zone)
    42  }
    43  
    44  // ExtraHeader encapsulates a key-value pairing of header key to header value.
    45  // It is stored as a struct{string, string} in memory as opposed to a
    46  // map[string]string to maintain ordering in a byte-for-byte encode/decode round
    47  // trip.
    48  type ExtraHeader struct {
    49  	// K is the header key, or the first run of bytes up until a ' ' (\x20)
    50  	// character.
    51  	K string
    52  	// V is the header value, or the remaining run of bytes in the line,
    53  	// stripping off the above "K" field as a prefix.
    54  	V string
    55  }
    56  
    57  // Commit encapsulates a Git commit entry.
    58  type Commit struct {
    59  	// Author is the Author this commit, or the original writer of the
    60  	// contents.
    61  	//
    62  	// NOTE: this field is stored as a string to ensure any extra "cruft"
    63  	// bytes are preserved through migration.
    64  	Author string
    65  	// Committer is the individual or entity that added this commit to the
    66  	// history.
    67  	//
    68  	// NOTE: this field is stored as a string to ensure any extra "cruft"
    69  	// bytes are preserved through migration.
    70  	Committer string
    71  	// ParentIDs are the IDs of all parents for which this commit is a
    72  	// linear child.
    73  	ParentIDs [][]byte
    74  	// TreeID is the root Tree associated with this commit.
    75  	TreeID []byte
    76  	// ExtraHeaders stores headers not listed above, for instance
    77  	// "encoding", "gpgsig", or "mergetag" (among others).
    78  	ExtraHeaders []*ExtraHeader
    79  	// Message is the commit message, including any signing information
    80  	// associated with this commit.
    81  	Message string
    82  }
    83  
    84  // Type implements Object.ObjectType by returning the correct object type for
    85  // Commits, CommitObjectType.
    86  func (c *Commit) Type() ObjectType { return CommitObjectType }
    87  
    88  // Decode implements Object.Decode and decodes the uncompressed commit being
    89  // read. It returns the number of uncompressed bytes being consumed off of the
    90  // stream, which should be strictly equal to the size given.
    91  //
    92  // If any error was encountered along the way, that will be returned, along with
    93  // the number of bytes read up to that point.
    94  func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
    95  	var finishedHeaders bool
    96  	var messageParts []string
    97  
    98  	s := bufio.NewScanner(from)
    99  	for s.Scan() {
   100  		text := s.Text()
   101  		n = n + len(text+"\n")
   102  
   103  		if len(s.Text()) == 0 && !finishedHeaders {
   104  			finishedHeaders = true
   105  			continue
   106  		}
   107  
   108  		if fields := strings.Fields(text); len(fields) > 0 && !finishedHeaders {
   109  			switch fields[0] {
   110  			case "tree":
   111  				id, err := hex.DecodeString(fields[1])
   112  				if err != nil {
   113  					return n, err
   114  				}
   115  				c.TreeID = id
   116  			case "parent":
   117  				id, err := hex.DecodeString(fields[1])
   118  				if err != nil {
   119  					return n, err
   120  				}
   121  				c.ParentIDs = append(c.ParentIDs, id)
   122  			case "author":
   123  				if len(text) >= 7 {
   124  					c.Author = text[7:]
   125  				} else {
   126  					c.Author = ""
   127  				}
   128  			case "committer":
   129  				if len(text) >= 10 {
   130  					c.Committer = text[10:]
   131  				} else {
   132  					c.Committer = ""
   133  				}
   134  			default:
   135  				c.ExtraHeaders = append(c.ExtraHeaders, &ExtraHeader{
   136  					K: fields[0],
   137  					V: strings.Join(fields[1:], " "),
   138  				})
   139  			}
   140  		} else {
   141  			messageParts = append(messageParts, s.Text())
   142  		}
   143  	}
   144  
   145  	c.Message = strings.Join(messageParts, "\n")
   146  
   147  	if err = s.Err(); err != nil {
   148  		return n, err
   149  	}
   150  	return n, err
   151  }
   152  
   153  // Encode encodes the commit's contents to the given io.Writer, "w". If there was
   154  // any error copying the commit's contents, that error will be returned.
   155  //
   156  // Otherwise, the number of bytes written will be returned.
   157  func (c *Commit) Encode(to io.Writer) (n int, err error) {
   158  	n, err = fmt.Fprintf(to, "tree %s\n", hex.EncodeToString(c.TreeID))
   159  	if err != nil {
   160  		return n, err
   161  	}
   162  
   163  	for _, pid := range c.ParentIDs {
   164  		n1, err := fmt.Fprintf(to, "parent %s\n", hex.EncodeToString(pid))
   165  		if err != nil {
   166  			return n, err
   167  		}
   168  
   169  		n = n + n1
   170  	}
   171  
   172  	n2, err := fmt.Fprintf(to, "author %s\ncommitter %s\n", c.Author, c.Committer)
   173  	if err != nil {
   174  		return n, err
   175  	}
   176  
   177  	n = n + n2
   178  
   179  	for _, hdr := range c.ExtraHeaders {
   180  		n3, err := fmt.Fprintf(to, "%s %s\n", hdr.K, hdr.V)
   181  		if err != nil {
   182  			return n, err
   183  		}
   184  
   185  		n = n + n3
   186  	}
   187  
   188  	n4, err := fmt.Fprintf(to, "\n%s", c.Message)
   189  	if err != nil {
   190  		return n, err
   191  	}
   192  
   193  	return n + n4, err
   194  }
   195  
   196  // Equal returns whether the receiving and given commits are equal, or in other
   197  // words, whether they are represented by the same SHA-1 when saved to the
   198  // object database.
   199  func (c *Commit) Equal(other *Commit) bool {
   200  	if (c == nil) != (other == nil) {
   201  		return false
   202  	}
   203  
   204  	if c != nil {
   205  		if len(c.ParentIDs) != len(other.ParentIDs) {
   206  			return false
   207  		}
   208  		for i := 0; i < len(c.ParentIDs); i++ {
   209  			p1 := c.ParentIDs[i]
   210  			p2 := other.ParentIDs[i]
   211  
   212  			if !bytes.Equal(p1, p2) {
   213  				return false
   214  			}
   215  		}
   216  
   217  		if len(c.ExtraHeaders) != len(other.ExtraHeaders) {
   218  			return false
   219  		}
   220  		for i := 0; i < len(c.ExtraHeaders); i++ {
   221  			e1 := c.ExtraHeaders[i]
   222  			e2 := other.ExtraHeaders[i]
   223  
   224  			if e1.K != e2.K || e1.V != e2.V {
   225  				return false
   226  			}
   227  		}
   228  
   229  		return c.Author == other.Author &&
   230  			c.Committer == other.Committer &&
   231  			c.Message == other.Message &&
   232  			bytes.Equal(c.TreeID, other.TreeID)
   233  	}
   234  	return true
   235  }