···11+package types
22+33+import (
44+ "bytes"
55+ "encoding/json"
66+ "fmt"
77+ "maps"
88+ "strings"
99+1010+ "github.com/go-git/go-git/v5/plumbing"
1111+ "github.com/go-git/go-git/v5/plumbing/object"
1212+)
1313+1414+type Commit struct {
1515+ // hash of the commit object.
1616+ Hash plumbing.Hash `json:"hash,omitempty"`
1717+1818+ // author is the original author of the commit.
1919+ Author object.Signature `json:"author"`
2020+2121+ // committer is the one performing the commit, might be different from author.
2222+ Committer object.Signature `json:"committer"`
2323+2424+ // message is the commit message, contains arbitrary text.
2525+ Message string `json:"message"`
2626+2727+ // treehash is the hash of the root tree of the commit.
2828+ Tree string `json:"tree"`
2929+3030+ // parents are the hashes of the parent commits of the commit.
3131+ ParentHashes []plumbing.Hash `json:"parent_hashes,omitempty"`
3232+3333+ // pgpsignature is the pgp signature of the commit.
3434+ PGPSignature string `json:"pgp_signature,omitempty"`
3535+3636+ // mergetag is the embedded tag object when a merge commit is created by
3737+ // merging a signed tag.
3838+ MergeTag string `json:"merge_tag,omitempty"`
3939+4040+ // changeid is a unique identifier for the change (e.g., gerrit change-id).
4141+ ChangeId string `json:"change_id,omitempty"`
4242+4343+ // extraheaders contains additional headers not captured by other fields.
4444+ ExtraHeaders map[string][]byte `json:"extra_headers,omitempty"`
4545+4646+ // deprecated: kept for backwards compatibility with old json format.
4747+ This string `json:"this,omitempty"`
4848+4949+ // deprecated: kept for backwards compatibility with old json format.
5050+ Parent string `json:"parent,omitempty"`
5151+}
5252+5353+// types.Commit is an unify two commit structs:
5454+// - git.object.Commit from
5555+// - types.NiceDiff.commit
5656+//
5757+// to do this in backwards compatible fashion, we define the base struct
5858+// to use the same fields as NiceDiff.Commit, and then we also unmarshal
5959+// the struct fields from go-git structs, this custom unmarshal makes sense
6060+// of both representations and unifies them to have maximal data in either
6161+// form.
6262+func (c *Commit) UnmarshalJSON(data []byte) error {
6363+ type Alias Commit
6464+6565+ aux := &struct {
6666+ *object.Commit
6767+ *Alias
6868+ }{
6969+ Alias: (*Alias)(c),
7070+ }
7171+7272+ if err := json.Unmarshal(data, aux); err != nil {
7373+ return err
7474+ }
7575+7676+ c.FromGoGitCommit(aux.Commit)
7777+7878+ return nil
7979+}
8080+8181+// fill in as much of Commit as possible from the given go-git commit
8282+func (c *Commit) FromGoGitCommit(gc *object.Commit) {
8383+ if gc == nil {
8484+ return
8585+ }
8686+8787+ if c.Hash.IsZero() {
8888+ c.Hash = gc.Hash
8989+ }
9090+ if c.This == "" {
9191+ c.This = gc.Hash.String()
9292+ }
9393+ if isEmptySignature(c.Author) {
9494+ c.Author = gc.Author
9595+ }
9696+ if isEmptySignature(c.Committer) {
9797+ c.Committer = gc.Committer
9898+ }
9999+ if c.Message == "" {
100100+ c.Message = gc.Message
101101+ }
102102+ if c.Tree == "" {
103103+ c.Tree = gc.TreeHash.String()
104104+ }
105105+ if c.PGPSignature == "" {
106106+ c.PGPSignature = gc.PGPSignature
107107+ }
108108+ if c.MergeTag == "" {
109109+ c.MergeTag = gc.MergeTag
110110+ }
111111+112112+ if len(c.ParentHashes) == 0 {
113113+ c.ParentHashes = gc.ParentHashes
114114+ }
115115+ if c.Parent == "" && len(gc.ParentHashes) > 0 {
116116+ c.Parent = gc.ParentHashes[0].String()
117117+ }
118118+119119+ if len(c.ExtraHeaders) == 0 {
120120+ c.ExtraHeaders = make(map[string][]byte)
121121+ maps.Copy(c.ExtraHeaders, gc.ExtraHeaders)
122122+ }
123123+124124+ if c.ChangeId == "" {
125125+ if v, ok := gc.ExtraHeaders["change-id"]; ok {
126126+ c.ChangeId = string(v)
127127+ }
128128+ }
129129+}
130130+131131+func isEmptySignature(s object.Signature) bool {
132132+ return s.Email == "" && s.Name == "" && s.When.IsZero()
133133+}
134134+135135+// produce a verifiable payload from this commit's metadata
136136+func (c *Commit) Payload() string {
137137+ author := bytes.NewBuffer([]byte{})
138138+ c.Author.Encode(author)
139139+140140+ committer := bytes.NewBuffer([]byte{})
141141+ c.Committer.Encode(committer)
142142+143143+ payload := strings.Builder{}
144144+145145+ fmt.Fprintf(&payload, "tree %s\n", c.Tree)
146146+147147+ if len(c.ParentHashes) > 0 {
148148+ for _, p := range c.ParentHashes {
149149+ fmt.Fprintf(&payload, "parent %s\n", p.String())
150150+ }
151151+ } else {
152152+ // present for backwards compatibility
153153+ fmt.Fprintf(&payload, "parent %s\n", c.Parent)
154154+ }
155155+156156+ fmt.Fprintf(&payload, "author %s\n", author.String())
157157+ fmt.Fprintf(&payload, "committer %s\n", committer.String())
158158+159159+ if c.ChangeId != "" {
160160+ fmt.Fprintf(&payload, "change-id %s\n", c.ChangeId)
161161+ } else if v, ok := c.ExtraHeaders["change-id"]; ok {
162162+ fmt.Fprintf(&payload, "change-id %s\n", string(v))
163163+ }
164164+165165+ fmt.Fprintf(&payload, "\n%s", c.Message)
166166+167167+ return payload.String()
168168+}