···1+package types
2+3+import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "maps"
8+ "strings"
9+10+ "github.com/go-git/go-git/v5/plumbing"
11+ "github.com/go-git/go-git/v5/plumbing/object"
12+)
13+14+type Commit struct {
15+ // hash of the commit object.
16+ Hash plumbing.Hash `json:"hash,omitempty"`
17+18+ // author is the original author of the commit.
19+ Author object.Signature `json:"author"`
20+21+ // committer is the one performing the commit, might be different from author.
22+ Committer object.Signature `json:"committer"`
23+24+ // message is the commit message, contains arbitrary text.
25+ Message string `json:"message"`
26+27+ // treehash is the hash of the root tree of the commit.
28+ Tree string `json:"tree"`
29+30+ // parents are the hashes of the parent commits of the commit.
31+ ParentHashes []plumbing.Hash `json:"parent_hashes,omitempty"`
32+33+ // pgpsignature is the pgp signature of the commit.
34+ PGPSignature string `json:"pgp_signature,omitempty"`
35+36+ // mergetag is the embedded tag object when a merge commit is created by
37+ // merging a signed tag.
38+ MergeTag string `json:"merge_tag,omitempty"`
39+40+ // changeid is a unique identifier for the change (e.g., gerrit change-id).
41+ ChangeId string `json:"change_id,omitempty"`
42+43+ // extraheaders contains additional headers not captured by other fields.
44+ ExtraHeaders map[string][]byte `json:"extra_headers,omitempty"`
45+46+ // deprecated: kept for backwards compatibility with old json format.
47+ This string `json:"this,omitempty"`
48+49+ // deprecated: kept for backwards compatibility with old json format.
50+ Parent string `json:"parent,omitempty"`
51+}
52+53+// types.Commit is an unify two commit structs:
54+// - git.object.Commit from
55+// - types.NiceDiff.commit
56+//
57+// to do this in backwards compatible fashion, we define the base struct
58+// to use the same fields as NiceDiff.Commit, and then we also unmarshal
59+// the struct fields from go-git structs, this custom unmarshal makes sense
60+// of both representations and unifies them to have maximal data in either
61+// form.
62+func (c *Commit) UnmarshalJSON(data []byte) error {
63+ type Alias Commit
64+65+ aux := &struct {
66+ *object.Commit
67+ *Alias
68+ }{
69+ Alias: (*Alias)(c),
70+ }
71+72+ if err := json.Unmarshal(data, aux); err != nil {
73+ return err
74+ }
75+76+ c.FromGoGitCommit(aux.Commit)
77+78+ return nil
79+}
80+81+// fill in as much of Commit as possible from the given go-git commit
82+func (c *Commit) FromGoGitCommit(gc *object.Commit) {
83+ if gc == nil {
84+ return
85+ }
86+87+ if c.Hash.IsZero() {
88+ c.Hash = gc.Hash
89+ }
90+ if c.This == "" {
91+ c.This = gc.Hash.String()
92+ }
93+ if isEmptySignature(c.Author) {
94+ c.Author = gc.Author
95+ }
96+ if isEmptySignature(c.Committer) {
97+ c.Committer = gc.Committer
98+ }
99+ if c.Message == "" {
100+ c.Message = gc.Message
101+ }
102+ if c.Tree == "" {
103+ c.Tree = gc.TreeHash.String()
104+ }
105+ if c.PGPSignature == "" {
106+ c.PGPSignature = gc.PGPSignature
107+ }
108+ if c.MergeTag == "" {
109+ c.MergeTag = gc.MergeTag
110+ }
111+112+ if len(c.ParentHashes) == 0 {
113+ c.ParentHashes = gc.ParentHashes
114+ }
115+ if c.Parent == "" && len(gc.ParentHashes) > 0 {
116+ c.Parent = gc.ParentHashes[0].String()
117+ }
118+119+ if len(c.ExtraHeaders) == 0 {
120+ c.ExtraHeaders = make(map[string][]byte)
121+ maps.Copy(c.ExtraHeaders, gc.ExtraHeaders)
122+ }
123+124+ if c.ChangeId == "" {
125+ if v, ok := gc.ExtraHeaders["change-id"]; ok {
126+ c.ChangeId = string(v)
127+ }
128+ }
129+}
130+131+func isEmptySignature(s object.Signature) bool {
132+ return s.Email == "" && s.Name == "" && s.When.IsZero()
133+}
134+135+// produce a verifiable payload from this commit's metadata
136+func (c *Commit) Payload() string {
137+ author := bytes.NewBuffer([]byte{})
138+ c.Author.Encode(author)
139+140+ committer := bytes.NewBuffer([]byte{})
141+ c.Committer.Encode(committer)
142+143+ payload := strings.Builder{}
144+145+ fmt.Fprintf(&payload, "tree %s\n", c.Tree)
146+147+ if len(c.ParentHashes) > 0 {
148+ for _, p := range c.ParentHashes {
149+ fmt.Fprintf(&payload, "parent %s\n", p.String())
150+ }
151+ } else {
152+ // present for backwards compatibility
153+ fmt.Fprintf(&payload, "parent %s\n", c.Parent)
154+ }
155+156+ fmt.Fprintf(&payload, "author %s\n", author.String())
157+ fmt.Fprintf(&payload, "committer %s\n", committer.String())
158+159+ if c.ChangeId != "" {
160+ fmt.Fprintf(&payload, "change-id %s\n", c.ChangeId)
161+ } else if v, ok := c.ExtraHeaders["change-id"]; ok {
162+ fmt.Fprintf(&payload, "change-id %s\n", string(v))
163+ }
164+165+ fmt.Fprintf(&payload, "\n%s", c.Message)
166+167+ return payload.String()
168+}