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 }