Monorepo for Tangled tangled.org

types: introduce Commit type

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>

authored by oppi.li and committed by Tangled da28eba8 ca8217e9

Changed files
+168
types
+168
types/commit.go
··· 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 + }