types: introduce Commit type #859

merged
opened by oppi.li targeting master from op/rkowwwsvpoqx

this is a backwards compatible structure that is capable of deserializing both go-git Commit structs and the existing types.NiceDiff.Commit struct.

Signed-off-by: oppiliappan me@oppi.li

Changed files
+168
types
+168
types/commit.go
···
+
package types
+
+
import (
+
"bytes"
+
"encoding/json"
+
"fmt"
+
"maps"
+
"strings"
+
+
"github.com/go-git/go-git/v5/plumbing"
+
"github.com/go-git/go-git/v5/plumbing/object"
+
)
+
+
type Commit struct {
+
// hash of the commit object.
+
Hash plumbing.Hash `json:"hash,omitempty"`
+
+
// author is the original author of the commit.
+
Author object.Signature `json:"author"`
+
+
// committer is the one performing the commit, might be different from author.
+
Committer object.Signature `json:"committer"`
+
+
// message is the commit message, contains arbitrary text.
+
Message string `json:"message"`
+
+
// treehash is the hash of the root tree of the commit.
+
Tree string `json:"tree"`
+
+
// parents are the hashes of the parent commits of the commit.
+
ParentHashes []plumbing.Hash `json:"parent_hashes,omitempty"`
+
+
// pgpsignature is the pgp signature of the commit.
+
PGPSignature string `json:"pgp_signature,omitempty"`
+
+
// mergetag is the embedded tag object when a merge commit is created by
+
// merging a signed tag.
+
MergeTag string `json:"merge_tag,omitempty"`
+
+
// changeid is a unique identifier for the change (e.g., gerrit change-id).
+
ChangeId string `json:"change_id,omitempty"`
+
+
// extraheaders contains additional headers not captured by other fields.
+
ExtraHeaders map[string][]byte `json:"extra_headers,omitempty"`
+
+
// deprecated: kept for backwards compatibility with old json format.
+
This string `json:"this,omitempty"`
+
+
// deprecated: kept for backwards compatibility with old json format.
+
Parent string `json:"parent,omitempty"`
+
}
+
+
// types.Commit is an unify two commit structs:
+
// - git.object.Commit from
+
// - types.NiceDiff.commit
+
//
+
// to do this in backwards compatible fashion, we define the base struct
+
// to use the same fields as NiceDiff.Commit, and then we also unmarshal
+
// the struct fields from go-git structs, this custom unmarshal makes sense
+
// of both representations and unifies them to have maximal data in either
+
// form.
+
func (c *Commit) UnmarshalJSON(data []byte) error {
+
type Alias Commit
+
+
aux := &struct {
+
*object.Commit
+
*Alias
+
}{
+
Alias: (*Alias)(c),
+
}
+
+
if err := json.Unmarshal(data, aux); err != nil {
+
return err
+
}
+
+
c.FromGoGitCommit(aux.Commit)
+
+
return nil
+
}
+
+
// fill in as much of Commit as possible from the given go-git commit
+
func (c *Commit) FromGoGitCommit(gc *object.Commit) {
+
if gc == nil {
+
return
+
}
+
+
if c.Hash.IsZero() {
+
c.Hash = gc.Hash
+
}
+
if c.This == "" {
+
c.This = gc.Hash.String()
+
}
+
if isEmptySignature(c.Author) {
+
c.Author = gc.Author
+
}
+
if isEmptySignature(c.Committer) {
+
c.Committer = gc.Committer
+
}
+
if c.Message == "" {
+
c.Message = gc.Message
+
}
+
if c.Tree == "" {
+
c.Tree = gc.TreeHash.String()
+
}
+
if c.PGPSignature == "" {
+
c.PGPSignature = gc.PGPSignature
+
}
+
if c.MergeTag == "" {
+
c.MergeTag = gc.MergeTag
+
}
+
+
if len(c.ParentHashes) == 0 {
+
c.ParentHashes = gc.ParentHashes
+
}
+
if c.Parent == "" && len(gc.ParentHashes) > 0 {
+
c.Parent = gc.ParentHashes[0].String()
+
}
+
+
if len(c.ExtraHeaders) == 0 {
+
c.ExtraHeaders = make(map[string][]byte)
+
maps.Copy(c.ExtraHeaders, gc.ExtraHeaders)
+
}
+
+
if c.ChangeId == "" {
+
if v, ok := gc.ExtraHeaders["change-id"]; ok {
+
c.ChangeId = string(v)
+
}
+
}
+
}
+
+
func isEmptySignature(s object.Signature) bool {
+
return s.Email == "" && s.Name == "" && s.When.IsZero()
+
}
+
+
// produce a verifiable payload from this commit's metadata
+
func (c *Commit) Payload() string {
+
author := bytes.NewBuffer([]byte{})
+
c.Author.Encode(author)
+
+
committer := bytes.NewBuffer([]byte{})
+
c.Committer.Encode(committer)
+
+
payload := strings.Builder{}
+
+
fmt.Fprintf(&payload, "tree %s\n", c.Tree)
+
+
if len(c.ParentHashes) > 0 {
+
for _, p := range c.ParentHashes {
+
fmt.Fprintf(&payload, "parent %s\n", p.String())
+
}
+
} else {
+
// present for backwards compatibility
+
fmt.Fprintf(&payload, "parent %s\n", c.Parent)
+
}
+
+
fmt.Fprintf(&payload, "author %s\n", author.String())
+
fmt.Fprintf(&payload, "committer %s\n", committer.String())
+
+
if c.ChangeId != "" {
+
fmt.Fprintf(&payload, "change-id %s\n", c.ChangeId)
+
} else if v, ok := c.ExtraHeaders["change-id"]; ok {
+
fmt.Fprintf(&payload, "change-id %s\n", string(v))
+
}
+
+
fmt.Fprintf(&payload, "\n%s", c.Message)
+
+
return payload.String()
+
}