git/odb: support git/odb.Commit.ExtraHeaders, byte-for-byte round trip

This commit is contained in:
Taylor Blau 2017-05-22 14:51:19 -06:00
parent b48f4026e8
commit cb5ab7c6d1
2 changed files with 58 additions and 10 deletions

@ -40,6 +40,19 @@ func (s *Signature) String() string {
return fmt.Sprintf("%s <%s> %d %s", s.Name, s.Email, at, zone)
}
// ExtraHeader encapsulates a key-value pairing of header key to header value.
// It is stored as a struct{string, string} in memory as opposed to a
// map[string]string to maintain ordering in a byte-for-byte encode/decode round
// trip.
type ExtraHeader struct {
// K is the header key, or the first run of bytes up until a ' ' (\x20)
// character.
K string
// V is the header value, or the remaining run of bytes in the line,
// stripping off the above "K" field as a prefix.
V string
}
// Commit encapsulates a Git commit entry.
type Commit struct {
// Author is the Author this commit, or the original writer of the
@ -59,6 +72,9 @@ type Commit struct {
ParentIDs [][]byte
// TreeID is the root Tree associated with this commit.
TreeID []byte
// ExtraHeaders stores headers not listed above, for instance
// "encoding", "gpgsig", or "mergetag" (among others).
ExtraHeaders []*ExtraHeader
// Message is the commit message, including any signing information
// associated with this commit.
Message string
@ -77,10 +93,16 @@ func (c *Commit) Type() ObjectType { return CommitObjectType }
// If any error was encountered along the way, that will be returned, along with
// the number of bytes read up to that point.
func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
var finishedHeaders bool
var messageParts []string
s := bufio.NewScanner(from)
for s.Scan() {
text := s.Text()
n = n + len(text+"\n")
if len(s.Text()) == 0 {
finishedHeaders = true
continue
}
@ -105,17 +127,20 @@ func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
case "committer":
c.Committer = strings.Join(fields[1:], " ")
default:
if len(c.Message) == 0 {
c.Message = s.Text()
if finishedHeaders {
messageParts = append(messageParts, s.Text())
} else {
c.Message = strings.Join([]string{c.Message, s.Text()}, "\n")
c.ExtraHeaders = append(c.ExtraHeaders, &ExtraHeader{
K: fields[0],
V: strings.Join(fields[1:], " "),
})
}
}
}
n = n + len(text+"\n")
}
c.Message = strings.Join(messageParts, "\n")
if err = s.Err(); err != nil {
return n, err
}
@ -141,11 +166,26 @@ func (c *Commit) Encode(to io.Writer) (n int, err error) {
n = n + n1
}
n2, err := fmt.Fprintf(to, "author %s\ncommitter %s\n\n%s\n",
c.Author, c.Committer, c.Message)
n2, err := fmt.Fprintf(to, "author %s\ncommitter %s\n", c.Author, c.Committer)
if err != nil {
return n, err
}
return n + n2, err
n = n + n2
for _, hdr := range c.ExtraHeaders {
n3, err := fmt.Fprintf(to, "%s %s\n", hdr.K, hdr.V)
if err != nil {
return n, err
}
n = n + n3
}
n4, err := fmt.Fprintf(to, "\n%s\n", c.Message)
if err != nil {
return n, err
}
return n + n4, err
}

@ -25,7 +25,10 @@ func TestCommitEncoding(t *testing.T) {
ParentIDs: [][]byte{
[]byte("aaaaaaaaaaaaaaaaaaaa"), []byte("bbbbbbbbbbbbbbbbbbbb"),
},
TreeID: []byte("cccccccccccccccccccc"),
TreeID: []byte("cccccccccccccccccccc"),
ExtraHeaders: []*ExtraHeader{
{"foo", "bar"},
},
Message: "initial commit",
}
@ -39,6 +42,7 @@ func TestCommitEncoding(t *testing.T) {
assertLine(t, buf, "parent 6262626262626262626262626262626262626262")
assertLine(t, buf, "author %s", author.String())
assertLine(t, buf, "committer %s", committer.String())
assertLine(t, buf, "foo bar")
assertLine(t, buf, "")
assertLine(t, buf, "initial commit")
@ -58,8 +62,9 @@ func TestCommitDecoding(t *testing.T) {
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "parent %s\n", hex.EncodeToString(p1))
fmt.Fprintf(from, "parent %s\n", hex.EncodeToString(p2))
fmt.Fprintf(from, "foo bar\n")
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "initial commit\n")
fmt.Fprintf(from, "\ninitial commit\n")
flen := from.Len()
@ -72,6 +77,9 @@ func TestCommitDecoding(t *testing.T) {
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, [][]byte{p1, p2}, commit.ParentIDs)
assert.Equal(t, 1, len(commit.ExtraHeaders))
assert.Equal(t, "foo", commit.ExtraHeaders[0].K)
assert.Equal(t, "bar", commit.ExtraHeaders[0].V)
assert.Equal(t, "initial commit", commit.Message)
}