···11+package types22+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 by3737+ // 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 from5555+// - types.NiceDiff.commit5656+//5757+// to do this in backwards compatible fashion, we define the base struct5858+// to use the same fields as NiceDiff.Commit, and then we also unmarshal5959+// the struct fields from go-git structs, this custom unmarshal makes sense6060+// of both representations and unifies them to have maximal data in either6161+// form.6262+func (c *Commit) UnmarshalJSON(data []byte) error {6363+ type Alias Commit6464+6565+ aux := &struct {6666+ *object.Commit6767+ *Alias6868+ }{6969+ Alias: (*Alias)(c),7070+ }7171+7272+ if err := json.Unmarshal(data, aux); err != nil {7373+ return err7474+ }7575+7676+ c.FromGoGitCommit(aux.Commit)7777+7878+ return nil7979+}8080+8181+// fill in as much of Commit as possible from the given go-git commit8282+func (c *Commit) FromGoGitCommit(gc *object.Commit) {8383+ if gc == nil {8484+ return8585+ }8686+8787+ if c.Hash.IsZero() {8888+ c.Hash = gc.Hash8989+ }9090+ if c.This == "" {9191+ c.This = gc.Hash.String()9292+ }9393+ if isEmptySignature(c.Author) {9494+ c.Author = gc.Author9595+ }9696+ if isEmptySignature(c.Committer) {9797+ c.Committer = gc.Committer9898+ }9999+ if c.Message == "" {100100+ c.Message = gc.Message101101+ }102102+ if c.Tree == "" {103103+ c.Tree = gc.TreeHash.String()104104+ }105105+ if c.PGPSignature == "" {106106+ c.PGPSignature = gc.PGPSignature107107+ }108108+ if c.MergeTag == "" {109109+ c.MergeTag = gc.MergeTag110110+ }111111+112112+ if len(c.ParentHashes) == 0 {113113+ c.ParentHashes = gc.ParentHashes114114+ }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 metadata136136+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 compatibility153153+ 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+}