back interdiff of round #1 and #0

appview/pages/markup: drop math support for markdown #868

merged
opened by boltless.me targeting master from sl/kkzqxsnttomz

Rendering specific LaTeX expression results infinite loop on renderer, causing entire appview to crash

Signed-off-by: Seongmin Lee git@boltless.me

files
appview
crypto
knotserver
git
xrpc
nix
patchutil
types
ERROR
appview/pages/markup/markdown.go

Failed to calculate interdiff for this file.

NEW
appview/commitverify/verify.go
··· 3 3 import ( 4 4 "log" 5 5 6 + "github.com/go-git/go-git/v5/plumbing/object" 6 7 "tangled.org/core/appview/db" 7 8 "tangled.org/core/appview/models" 8 9 "tangled.org/core/crypto" ··· 34 35 return "" 35 36 } 36 37 37 - func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.Commit) (VerifiedCommits, error) { 38 + func GetVerifiedObjectCommits(e db.Execer, emailToDid map[string]string, commits []*object.Commit) (VerifiedCommits, error) { 39 + ndCommits := []types.NiceDiff{} 40 + for _, commit := range commits { 41 + ndCommits = append(ndCommits, ObjectCommitToNiceDiff(commit)) 42 + } 43 + return GetVerifiedCommits(e, emailToDid, ndCommits) 44 + } 45 + 46 + func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.NiceDiff) (VerifiedCommits, error) { 38 47 vcs := VerifiedCommits{} 39 48 40 49 didPubkeyCache := make(map[string][]models.PublicKey) 41 50 42 51 for _, commit := range ndCommits { 43 - committerEmail := commit.Committer.Email 52 + c := commit.Commit 53 + 54 + committerEmail := c.Committer.Email 44 55 if did, exists := emailToDid[committerEmail]; exists { 45 56 // check if we've already fetched public keys for this did 46 57 pubKeys, ok := didPubkeyCache[did] ··· 56 67 } 57 68 58 69 // try to verify with any associated pubkeys 59 - payload := commit.Payload() 60 - signature := commit.PGPSignature 61 70 for _, pk := range pubKeys { 62 - if _, ok := crypto.VerifySignature([]byte(pk.Key), []byte(signature), []byte(payload)); ok { 71 + if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok { 63 72 64 73 fp, err := crypto.SSHFingerprint(pk.Key) 65 74 if err != nil { 66 75 log.Println("error computing ssh fingerprint:", err) 67 76 } 68 77 69 - vc := verifiedCommit{fingerprint: fp, hash: commit.This} 78 + vc := verifiedCommit{fingerprint: fp, hash: c.This} 70 79 vcs[vc] = struct{}{} 71 80 break 72 81 } ··· 77 86 78 87 return vcs, nil 79 88 } 89 + 90 + // ObjectCommitToNiceDiff is a compatibility function to convert a 91 + // commit object into a NiceDiff structure. 92 + func ObjectCommitToNiceDiff(c *object.Commit) types.NiceDiff { 93 + var niceDiff types.NiceDiff 94 + 95 + // set commit information 96 + niceDiff.Commit.Message = c.Message 97 + niceDiff.Commit.Author = c.Author 98 + niceDiff.Commit.This = c.Hash.String() 99 + niceDiff.Commit.Committer = c.Committer 100 + niceDiff.Commit.Tree = c.TreeHash.String() 101 + niceDiff.Commit.PGPSignature = c.PGPSignature 102 + 103 + changeId, ok := c.ExtraHeaders["change-id"] 104 + if ok { 105 + niceDiff.Commit.ChangedId = string(changeId) 106 + } 107 + 108 + // set parent hash if available 109 + if len(c.ParentHashes) > 0 { 110 + niceDiff.Commit.Parent = c.ParentHashes[0].String() 111 + } 112 + 113 + // XXX: Stats and Diff fields are typically populated 114 + // after fetching the actual diff information, which isn't 115 + // directly available in the commit object itself. 116 + 117 + return niceDiff 118 + }
NEW
appview/pages/funcmap.go
··· 162 162 } 163 163 return pairs, nil 164 164 }, 165 - "append": func(s []any, values ...any) []any { 165 + "append": func(s []string, values ...string) []string { 166 166 s = append(s, values...) 167 167 return s 168 168 },
NEW
appview/pages/pages.go
··· 31 31 "github.com/bluesky-social/indigo/atproto/identity" 32 32 "github.com/bluesky-social/indigo/atproto/syntax" 33 33 "github.com/go-git/go-git/v5/plumbing" 34 + "github.com/go-git/go-git/v5/plumbing/object" 34 35 ) 35 36 36 37 //go:embed templates/* static legal ··· 648 649 RepoInfo repoinfo.RepoInfo 649 650 Active string 650 651 TagMap map[string][]string 651 - CommitsTrunc []types.Commit 652 + CommitsTrunc []*object.Commit 652 653 TagsTrunc []*types.TagReference 653 654 BranchesTrunc []types.Branch 654 655 // ForkInfo *types.ForkInfo
NEW
appview/pages/templates/fragments/tinyAvatarList.html
··· 1 - {{ define "fragments/tinyAvatarList" }} 2 - {{ $all := .all }} 3 - {{ $classes := .classes }} 4 - {{ $ps := take $all 5 }} 5 - <div class="inline-flex items-center -space-x-3"> 6 - {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 7 - {{ range $i, $p := $ps }} 8 - <img 9 - src="{{ tinyAvatar . }}" 10 - alt="" 11 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0 {{ $classes }}" 12 - /> 13 - {{ end }} 14 - 15 - {{ if gt (len $all) 5 }} 16 - <span class="pl-4 text-gray-500 dark:text-gray-400 text-sm"> 17 - +{{ sub (len $all) 5 }} 18 - </span> 19 - {{ end }} 20 - </div> 21 - {{ end }} 22 -
NEW
appview/pages/templates/repo/commit.html
··· 25 25 </div> 26 26 27 27 <div class="flex flex-wrap items-center space-x-2"> 28 - <p class="flex flex-wrap items-center gap-1 text-sm text-gray-500 dark:text-gray-300"> 29 - {{ template "attribution" . }} 28 + <p class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-300"> 29 + {{ $did := index $.EmailToDid $commit.Author.Email }} 30 + 31 + {{ if $did }} 32 + {{ template "user/fragments/picHandleLink" $did }} 33 + {{ else }} 34 + <a href="mailto:{{ $commit.Author.Email }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ $commit.Author.Name }}</a> 35 + {{ end }} 30 36 31 37 <span class="px-1 select-none before:content-['\00B7']"></span> 32 - {{ template "repo/fragments/time" $commit.Committer.When }} 38 + {{ template "repo/fragments/time" $commit.Author.When }} 33 39 <span class="px-1 select-none before:content-['\00B7']"></span> 34 40 35 41 <a href="/{{ $repo }}/commit/{{ $commit.This }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.This 0 8 }}</a> ··· 72 78 73 79 </section> 74 80 {{end}} 75 - 76 - {{ define "attribution" }} 77 - {{ $commit := .Diff.Commit }} 78 - {{ $showCommitter := true }} 79 - {{ if eq $commit.Author.Email $commit.Committer.Email }} 80 - {{ $showCommitter = false }} 81 - {{ end }} 82 - 83 - {{ if $showCommitter }} 84 - authored by {{ template "attributedUser" (list $commit.Author.Email $commit.Author.Name $.EmailToDid) }} 85 - {{ range $commit.CoAuthors }} 86 - {{ template "attributedUser" (list .Email .Name $.EmailToDid) }} 87 - {{ end }} 88 - and committed by {{ template "attributedUser" (list $commit.Committer.Email $commit.Committer.Name $.EmailToDid) }} 89 - {{ else }} 90 - {{ template "attributedUser" (list $commit.Author.Email $commit.Author.Name $.EmailToDid )}} 91 - {{ end }} 92 - {{ end }} 93 - 94 - {{ define "attributedUser" }} 95 - {{ $email := index . 0 }} 96 - {{ $name := index . 1 }} 97 - {{ $map := index . 2 }} 98 - {{ $did := index $map $email }} 99 - 100 - {{ if $did }} 101 - {{ template "user/fragments/picHandleLink" $did }} 102 - {{ else }} 103 - <a href="mailto:{{ $email }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ $name }}</a> 104 - {{ end }} 105 - {{ end }} 106 81 107 82 {{ define "topbarLayout" }} 108 83 <header class="col-span-full" style="z-index: 20;">
NEW
appview/pages/templates/repo/fragments/participants.html
··· 6 6 <span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span> 7 7 <span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span> 8 8 </div> 9 - {{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "w-8 h-8") }} 9 + <div class="flex items-center -space-x-3 mt-2"> 10 + {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 11 + {{ range $i, $p := $ps }} 12 + <img 13 + src="{{ tinyAvatar . }}" 14 + alt="" 15 + class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0" 16 + /> 17 + {{ end }} 18 + 19 + {{ if gt (len $all) 5 }} 20 + <span class="pl-4 text-gray-500 dark:text-gray-400 text-sm"> 21 + +{{ sub (len $all) 5 }} 22 + </span> 23 + {{ end }} 24 + </div> 10 25 </div> 11 26 {{ end }}
NEW
appview/pages/templates/repo/index.html
··· 14 14 {{ end }} 15 15 <div class="flex items-center justify-between pb-5"> 16 16 {{ block "branchSelector" . }}{{ end }} 17 - <div class="flex md:hidden items-center gap-3"> 17 + <div class="flex md:hidden items-center gap-2"> 18 18 <a href="/{{ .RepoInfo.FullName }}/commits/{{ .Ref | urlquery }}" class="inline-flex items-center text-sm gap-1 font-bold"> 19 19 {{ i "git-commit-horizontal" "w-4" "h-4" }} {{ .TotalCommits }} 20 20 </a> ··· 66 66 67 67 {{ define "branchSelector" }} 68 68 <div class="flex gap-2 items-center justify-between w-full"> 69 - <div class="flex gap-2 items-stretch"> 69 + <div class="flex gap-2 items-center"> 70 70 <select 71 71 onchange="window.location.href = '/{{ .RepoInfo.FullName }}/tree/' + encodeURIComponent(this.value)" 72 72 class="p-1 border max-w-32 border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700" ··· 228 228 <span 229 229 class="mx-1 before:content-['·'] before:select-none" 230 230 ></span> 231 - {{ template "attribution" (list . $.EmailToDid) }} 231 + <span> 232 + {{ $did := index $.EmailToDid .Author.Email }} 233 + <a href="{{ if $did }}/{{ resolve $did }}{{ else }}mailto:{{ .Author.Email }}{{ end }}" 234 + class="text-gray-500 dark:text-gray-400 no-underline hover:underline" 235 + >{{ if $did }}{{ template "user/fragments/picHandleLink" $did }}{{ else }}{{ .Author.Name }}{{ end }}</a> 236 + </span> 232 237 <div class="inline-block px-1 select-none after:content-['·']"></div> 233 238 {{ template "repo/fragments/time" .Committer.When }} 234 239 ··· 254 259 {{ end }} 255 260 </div> 256 261 </div> 257 - {{ end }} 258 - 259 - {{ define "attribution" }} 260 - {{ $commit := index . 0 }} 261 - {{ $map := index . 1 }} 262 - <span class="flex items-center"> 263 - {{ $author := index $map $commit.Author.Email }} 264 - {{ $coauthors := $commit.CoAuthors }} 265 - {{ $all := list }} 266 - 267 - {{ if $author }} 268 - {{ $all = append $all $author }} 269 - {{ end }} 270 - {{ range $coauthors }} 271 - {{ $co := index $map .Email }} 272 - {{ if $co }} 273 - {{ $all = append $all $co }} 274 - {{ end }} 275 - {{ end }} 276 - 277 - {{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "size-6") }} 278 - <a href="{{ if $author }}/{{ $author }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}" 279 - class="no-underline hover:underline"> 280 - {{ if $author }}{{ resolve $author }}{{ else }}{{ $commit.Author.Name }}{{ end }} 281 - {{ if $coauthors }} +{{ length $coauthors }}{{ end }} 282 - </a> 283 - </span> 284 262 {{ end }} 285 263 286 264 {{ define "branchList" }}
NEW
appview/pages/templates/repo/log.html
··· 17 17 <div class="hidden md:flex md:flex-col divide-y divide-gray-200 dark:divide-gray-700"> 18 18 {{ $grid := "grid grid-cols-14 gap-4" }} 19 19 <div class="{{ $grid }}"> 20 - <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-3">Author</div> 20 + <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-2">Author</div> 21 21 <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-3">Commit</div> 22 22 <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-6">Message</div> 23 + <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-1"></div> 23 24 <div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-2 justify-self-end">Date</div> 24 25 </div> 25 26 {{ range $index, $commit := .Commits }} 26 27 {{ $messageParts := splitN $commit.Message "\n\n" 2 }} 27 28 <div class="{{ $grid }} py-3"> 28 - <div class="align-top col-span-3"> 29 - {{ template "attribution" (list $commit $.EmailToDid) }} 29 + <div class="align-top truncate col-span-2"> 30 + {{ $did := index $.EmailToDid $commit.Author.Email }} 31 + {{ if $did }} 32 + {{ template "user/fragments/picHandleLink" $did }} 33 + {{ else }} 34 + <a href="mailto:{{ $commit.Author.Email }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $commit.Author.Name }}</a> 35 + {{ end }} 30 36 </div> 31 37 <div class="align-top font-mono flex items-start col-span-3"> 32 38 {{ $verified := $.VerifiedCommits.IsVerified $commit.Hash.String }} ··· 55 61 <div class="align-top col-span-6"> 56 62 <div> 57 63 <a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" class="dark:text-white no-underline hover:underline">{{ index $messageParts 0 }}</a> 58 - 59 64 {{ if gt (len $messageParts) 1 }} 60 65 <button class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 rounded" hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')">{{ i "ellipsis" "w-3 h-3" }}</button> 61 66 {{ end }} ··· 67 72 </span> 68 73 {{ end }} 69 74 {{ end }} 70 - 71 - <!-- ci status --> 72 - <span class="text-xs"> 73 - {{ $pipeline := index $.Pipelines .Hash.String }} 74 - {{ if and $pipeline (gt (len $pipeline.Statuses) 0) }} 75 - {{ template "repo/pipelines/fragments/pipelineSymbolLong" (dict "Pipeline" $pipeline "RepoInfo" $.RepoInfo) }} 76 - {{ end }} 77 - </span> 78 75 </div> 79 76 80 77 {{ if gt (len $messageParts) 1 }} 81 78 <p class="hidden mt-1 text-sm text-gray-600 dark:text-gray-400">{{ nl2br (index $messageParts 1) }}</p> 82 79 {{ end }} 80 + </div> 81 + <div class="align-top col-span-1"> 82 + <!-- ci status --> 83 + {{ $pipeline := index $.Pipelines .Hash.String }} 84 + {{ if and $pipeline (gt (len $pipeline.Statuses) 0) }} 85 + {{ template "repo/pipelines/fragments/pipelineSymbolLong" (dict "Pipeline" $pipeline "RepoInfo" $.RepoInfo) }} 86 + {{ end }} 83 87 </div> 84 88 <div class="align-top justify-self-end text-gray-500 dark:text-gray-400 col-span-2">{{ template "repo/fragments/shortTimeAgo" $commit.Committer.When }}</div> 85 89 </div> ··· 148 152 </a> 149 153 </span> 150 154 <span class="mx-2 before:content-['·'] before:select-none"></span> 151 - {{ template "attribution" (list $commit $.EmailToDid) }} 155 + <span> 156 + {{ $did := index $.EmailToDid $commit.Author.Email }} 157 + <a href="{{ if $did }}/{{ $did }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}" 158 + class="text-gray-500 dark:text-gray-400 no-underline hover:underline"> 159 + {{ if $did }}{{ template "user/fragments/picHandleLink" $did }}{{ else }}{{ $commit.Author.Name }}{{ end }} 160 + </a> 161 + </span> 152 162 <div class="inline-block px-1 select-none after:content-['·']"></div> 153 163 <span>{{ template "repo/fragments/shortTime" $commit.Committer.When }}</span> 154 164 ··· 166 176 </div> 167 177 </section> 168 178 169 - {{ end }} 170 - 171 - {{ define "attribution" }} 172 - {{ $commit := index . 0 }} 173 - {{ $map := index . 1 }} 174 - <span class="flex items-center gap-1"> 175 - {{ $author := index $map $commit.Author.Email }} 176 - {{ $coauthors := $commit.CoAuthors }} 177 - {{ $all := list }} 178 - 179 - {{ if $author }} 180 - {{ $all = append $all $author }} 181 - {{ end }} 182 - {{ range $coauthors }} 183 - {{ $co := index $map .Email }} 184 - {{ if $co }} 185 - {{ $all = append $all $co }} 186 - {{ end }} 187 - {{ end }} 188 - 189 - {{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "size-6") }} 190 - <a href="{{ if $author }}/{{ $author }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}" 191 - class="no-underline hover:underline"> 192 - {{ if $author }}{{ resolve $author }}{{ else }}{{ $commit.Author.Name }}{{ end }} 193 - {{ if $coauthors }} +{{ length $coauthors }}{{ end }} 194 - </a> 195 - </span> 196 179 {{ end }} 197 180 198 181 {{ define "repoAfter" }}
NEW
appview/repo/index.go
··· 122 122 l.Error("failed to get email to did map", "err", err) 123 123 } 124 124 125 - vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, commitsTrunc) 125 + vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, commitsTrunc) 126 126 if err != nil { 127 127 l.Error("failed to GetVerifiedObjectCommits", "err", err) 128 128 }
NEW
appview/repo/log.go
··· 116 116 l.Error("failed to fetch email to did mapping", "err", err) 117 117 } 118 118 119 - vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, xrpcResp.Commits) 119 + vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits) 120 120 if err != nil { 121 121 l.Error("failed to GetVerifiedObjectCommits", "err", err) 122 122 } ··· 192 192 l.Error("failed to get email to did mapping", "err", err) 193 193 } 194 194 195 - vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit}) 195 + vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.NiceDiff{*result.Diff}) 196 196 if err != nil { 197 197 l.Error("failed to GetVerifiedCommits", "err", err) 198 198 }
NEW
appview/repo/repo_util.go
··· 1 1 package repo 2 2 3 3 import ( 4 - "maps" 5 4 "slices" 6 5 "sort" 7 6 "strings" ··· 9 8 "tangled.org/core/appview/db" 10 9 "tangled.org/core/appview/models" 11 10 "tangled.org/core/types" 11 + 12 + "github.com/go-git/go-git/v5/plumbing/object" 12 13 ) 13 14 14 15 func sortFiles(files []types.NiceTree) { ··· 41 42 }) 42 43 } 43 44 44 - func uniqueEmails(commits []types.Commit) []string { 45 + func uniqueEmails(commits []*object.Commit) []string { 45 46 emails := make(map[string]struct{}) 46 47 for _, commit := range commits { 47 - emails[commit.Author.Email] = struct{}{} 48 - emails[commit.Committer.Email] = struct{}{} 49 - for _, c := range commit.CoAuthors() { 50 - emails[c.Email] = struct{}{} 48 + if commit.Author.Email != "" { 49 + emails[commit.Author.Email] = struct{}{} 50 + } 51 + if commit.Committer.Email != "" { 52 + emails[commit.Committer.Email] = struct{}{} 51 53 } 52 54 } 53 - 54 - // delete empty emails if any, from the set 55 - delete(emails, "") 56 - 57 - return slices.Collect(maps.Keys(emails)) 55 + var uniqueEmails []string 56 + for email := range emails { 57 + uniqueEmails = append(uniqueEmails, email) 58 + } 59 + return uniqueEmails 58 60 } 59 61 60 62 func balanceIndexItems(commitCount, branchCount, tagCount, fileCount int) (commitsTrunc int, branchesTrunc int, tagsTrunc int) {
NEW
crypto/verify.go
··· 5 5 "crypto/sha256" 6 6 "encoding/base64" 7 7 "fmt" 8 + "strings" 8 9 9 10 "github.com/hiddeco/sshsig" 10 11 "golang.org/x/crypto/ssh" 12 + "tangled.org/core/types" 11 13 ) 12 14 13 15 func VerifySignature(pubKey, signature, payload []byte) (error, bool) { ··· 26 28 // multiple algorithms but sha-512 is most secure, and git's ssh signing defaults 27 29 // to sha-512 for all key types anyway. 28 30 err = sshsig.Verify(buf, sig, pub, sshsig.HashSHA512, "git") 31 + return err, err == nil 32 + } 29 33 30 - return err, err == nil 34 + // VerifyCommitSignature reconstructs the payload used to sign a commit. This is 35 + // essentially the git cat-file output but without the gpgsig header. 36 + // 37 + // Caveats: signature verification will fail on commits with more than one parent, 38 + // i.e. merge commits, because types.NiceDiff doesn't carry more than one Parent field 39 + // and we are unable to reconstruct the payload correctly. 40 + // 41 + // Ideally this should directly operate on an *object.Commit. 42 + func VerifyCommitSignature(pubKey string, commit types.NiceDiff) (error, bool) { 43 + signature := commit.Commit.PGPSignature 44 + 45 + author := bytes.NewBuffer([]byte{}) 46 + committer := bytes.NewBuffer([]byte{}) 47 + commit.Commit.Author.Encode(author) 48 + commit.Commit.Committer.Encode(committer) 49 + 50 + payload := strings.Builder{} 51 + 52 + fmt.Fprintf(&payload, "tree %s\n", commit.Commit.Tree) 53 + if commit.Commit.Parent != "" { 54 + fmt.Fprintf(&payload, "parent %s\n", commit.Commit.Parent) 55 + } 56 + fmt.Fprintf(&payload, "author %s\n", author.String()) 57 + fmt.Fprintf(&payload, "committer %s\n", committer.String()) 58 + if commit.Commit.ChangedId != "" { 59 + fmt.Fprintf(&payload, "change-id %s\n", commit.Commit.ChangedId) 60 + } 61 + fmt.Fprintf(&payload, "\n%s", commit.Commit.Message) 62 + 63 + return VerifySignature([]byte(pubKey), []byte(signature), []byte(payload.String())) 31 64 } 32 65 33 66 // SSHFingerprint computes the fingerprint of the supplied ssh pubkey.
NEW
go.mod
··· 44 44 github.com/stretchr/testify v1.10.0 45 45 github.com/urfave/cli/v3 v3.3.3 46 46 github.com/whyrusleeping/cbor-gen v0.3.1 47 - github.com/wyatt915/goldmark-treeblood v0.0.1 48 47 github.com/yuin/goldmark v1.7.13 49 48 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 50 49 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab ··· 190 189 github.com/vmihailenco/go-tinylfu v0.2.2 // indirect 191 190 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 192 191 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 193 - github.com/wyatt915/treeblood v0.1.16 // indirect 194 192 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 195 193 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 196 194 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
NEW
go.sum
··· 495 495 github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 496 496 github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= 497 497 github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 498 - github.com/wyatt915/goldmark-treeblood v0.0.1 h1:6vLJcjFrHgE4ASu2ga4hqIQmbvQLU37v53jlHZ3pqDs= 499 - github.com/wyatt915/goldmark-treeblood v0.0.1/go.mod h1:SmcJp5EBaV17rroNlgNQFydYwy0+fv85CUr/ZaCz208= 500 - github.com/wyatt915/treeblood v0.1.16 h1:byxNbWZhnPDxdTp7W5kQhCeaY8RBVmojTFz1tEHgg8Y= 501 - github.com/wyatt915/treeblood v0.1.16/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY= 502 498 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 503 499 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 504 500 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
NEW
knotserver/git/diff.go
··· 77 77 nd.Diff = append(nd.Diff, ndiff) 78 78 } 79 79 80 - nd.Commit.FromGoGitCommit(c) 80 + nd.Stat.FilesChanged = len(diffs) 81 + nd.Commit.This = c.Hash.String() 82 + nd.Commit.PGPSignature = c.PGPSignature 83 + nd.Commit.Committer = c.Committer 84 + nd.Commit.Tree = c.TreeHash.String() 85 + 86 + if parent.Hash.IsZero() { 87 + nd.Commit.Parent = "" 88 + } else { 89 + nd.Commit.Parent = parent.Hash.String() 90 + } 91 + nd.Commit.Author = c.Author 92 + nd.Commit.Message = c.Message 93 + 94 + if v, ok := c.ExtraHeaders["change-id"]; ok { 95 + nd.Commit.ChangedId = string(v) 96 + } 81 97 82 98 return &nd, nil 83 99 }
NEW
knotserver/xrpc/repo_log.go
··· 62 62 return 63 63 } 64 64 65 - tcommits := make([]types.Commit, len(commits)) 66 - for i, c := range commits { 67 - tcommits[i].FromGoGitCommit(c) 68 - } 69 - 70 65 // Create response using existing types.RepoLogResponse 71 66 response := types.RepoLogResponse{ 72 - Commits: tcommits, 67 + Commits: commits, 73 68 Ref: ref, 74 69 Page: (offset / limit) + 1, 75 70 PerPage: limit,
NEW
nix/gomod2nix.toml
··· 165 165 [mod."github.com/davecgh/go-spew"] 166 166 version = "v1.1.2-0.20180830191138-d8f796af33cc" 167 167 hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc=" 168 - [mod."github.com/decred/dcrd/dcrec/secp256k1/v4"] 169 - version = "v4.4.0" 170 - hash = "sha256-qrhEIwhDll3cxoVpMbm1NQ9/HTI42S7ms8Buzlo5HCg=" 171 168 [mod."github.com/dgraph-io/ristretto"] 172 169 version = "v0.2.0" 173 170 hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw=" ··· 373 370 [mod."github.com/klauspost/cpuid/v2"] 374 371 version = "v2.3.0" 375 372 hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc=" 376 - [mod."github.com/lestrrat-go/blackmagic"] 377 - version = "v1.0.4" 378 - hash = "sha256-HmWOpwoPDNMwLdOi7onNn3Sb+ZsAa3Ai3gVBbXmQ0e8=" 379 - [mod."github.com/lestrrat-go/httpcc"] 380 - version = "v1.0.1" 381 - hash = "sha256-SMRSwJpqDIs/xL0l2e8vP0W65qtCHX2wigcOeqPJmos=" 382 - [mod."github.com/lestrrat-go/httprc"] 383 - version = "v1.0.6" 384 - hash = "sha256-mfZzePEhrmyyu/avEBd2MsDXyto8dq5+fyu5lA8GUWM=" 385 - [mod."github.com/lestrrat-go/iter"] 386 - version = "v1.0.2" 387 - hash = "sha256-30tErRf7Qu/NOAt1YURXY/XJSA6sCr6hYQfO8QqHrtw=" 388 - [mod."github.com/lestrrat-go/jwx/v2"] 389 - version = "v2.1.6" 390 - hash = "sha256-0LszXRZIba+X8AOrs3T4uanAUafBdlVB8/MpUNEFpbc=" 391 - [mod."github.com/lestrrat-go/option"] 392 - version = "v1.0.1" 393 - hash = "sha256-jVcIYYVsxElIS/l2akEw32vdEPR8+anR6oeT1FoYULI=" 394 373 [mod."github.com/lucasb-eyer/go-colorful"] 395 374 version = "v1.2.0" 396 375 hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" ··· 511 490 [mod."github.com/ryanuber/go-glob"] 512 491 version = "v1.0.0" 513 492 hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY=" 514 - [mod."github.com/segmentio/asm"] 515 - version = "v1.2.0" 516 - hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs=" 517 493 [mod."github.com/sergi/go-diff"] 518 494 version = "v1.1.0" 519 495 hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY=" ··· 548 524 [mod."github.com/whyrusleeping/cbor-gen"] 549 525 version = "v0.3.1" 550 526 hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc=" 551 - [mod."github.com/wyatt915/goldmark-treeblood"] 552 - version = "v0.0.1" 553 - hash = "sha256-hAVFaktO02MiiqZFffr8ZlvFEfwxw4Y84OZ2t7e5G7g=" 554 - [mod."github.com/wyatt915/treeblood"] 555 - version = "v0.1.16" 556 - hash = "sha256-T68sa+iVx0qY7dDjXEAJvRWQEGXYIpUsf9tcWwO1tIw=" 557 527 [mod."github.com/xo/terminfo"] 558 528 version = "v0.0.0-20220910002029-abceb7e1c41e" 559 529 hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU="
NEW
patchutil/patchutil.go
··· 296 296 } 297 297 298 298 nd := types.NiceDiff{} 299 + nd.Commit.Parent = targetBranch 299 300 300 301 for _, d := range diffs { 301 302 ndiff := types.Diff{}
NEW
types/commit.go
··· 1 - package types 2 - 3 - import ( 4 - "bytes" 5 - "encoding/json" 6 - "fmt" 7 - "maps" 8 - "regexp" 9 - "strings" 10 - 11 - "github.com/go-git/go-git/v5/plumbing" 12 - "github.com/go-git/go-git/v5/plumbing/object" 13 - ) 14 - 15 - type Commit struct { 16 - // hash of the commit object. 17 - Hash plumbing.Hash `json:"hash,omitempty"` 18 - 19 - // author is the original author of the commit. 20 - Author object.Signature `json:"author"` 21 - 22 - // committer is the one performing the commit, might be different from author. 23 - Committer object.Signature `json:"committer"` 24 - 25 - // message is the commit message, contains arbitrary text. 26 - Message string `json:"message"` 27 - 28 - // treehash is the hash of the root tree of the commit. 29 - Tree string `json:"tree"` 30 - 31 - // parents are the hashes of the parent commits of the commit. 32 - ParentHashes []plumbing.Hash `json:"parent_hashes,omitempty"` 33 - 34 - // pgpsignature is the pgp signature of the commit. 35 - PGPSignature string `json:"pgp_signature,omitempty"` 36 - 37 - // mergetag is the embedded tag object when a merge commit is created by 38 - // merging a signed tag. 39 - MergeTag string `json:"merge_tag,omitempty"` 40 - 41 - // changeid is a unique identifier for the change (e.g., gerrit change-id). 42 - ChangeId string `json:"change_id,omitempty"` 43 - 44 - // extraheaders contains additional headers not captured by other fields. 45 - ExtraHeaders map[string][]byte `json:"extra_headers,omitempty"` 46 - 47 - // deprecated: kept for backwards compatibility with old json format. 48 - This string `json:"this,omitempty"` 49 - 50 - // deprecated: kept for backwards compatibility with old json format. 51 - Parent string `json:"parent,omitempty"` 52 - } 53 - 54 - // types.Commit is an unify two commit structs: 55 - // - git.object.Commit from 56 - // - types.NiceDiff.commit 57 - // 58 - // to do this in backwards compatible fashion, we define the base struct 59 - // to use the same fields as NiceDiff.Commit, and then we also unmarshal 60 - // the struct fields from go-git structs, this custom unmarshal makes sense 61 - // of both representations and unifies them to have maximal data in either 62 - // form. 63 - func (c *Commit) UnmarshalJSON(data []byte) error { 64 - type Alias Commit 65 - 66 - aux := &struct { 67 - *object.Commit 68 - *Alias 69 - }{ 70 - Alias: (*Alias)(c), 71 - } 72 - 73 - if err := json.Unmarshal(data, aux); err != nil { 74 - return err 75 - } 76 - 77 - c.FromGoGitCommit(aux.Commit) 78 - 79 - return nil 80 - } 81 - 82 - // fill in as much of Commit as possible from the given go-git commit 83 - func (c *Commit) FromGoGitCommit(gc *object.Commit) { 84 - if gc == nil { 85 - return 86 - } 87 - 88 - if c.Hash.IsZero() { 89 - c.Hash = gc.Hash 90 - } 91 - if c.This == "" { 92 - c.This = gc.Hash.String() 93 - } 94 - if isEmptySignature(c.Author) { 95 - c.Author = gc.Author 96 - } 97 - if isEmptySignature(c.Committer) { 98 - c.Committer = gc.Committer 99 - } 100 - if c.Message == "" { 101 - c.Message = gc.Message 102 - } 103 - if c.Tree == "" { 104 - c.Tree = gc.TreeHash.String() 105 - } 106 - if c.PGPSignature == "" { 107 - c.PGPSignature = gc.PGPSignature 108 - } 109 - if c.MergeTag == "" { 110 - c.MergeTag = gc.MergeTag 111 - } 112 - 113 - if len(c.ParentHashes) == 0 { 114 - c.ParentHashes = gc.ParentHashes 115 - } 116 - if c.Parent == "" && len(gc.ParentHashes) > 0 { 117 - c.Parent = gc.ParentHashes[0].String() 118 - } 119 - 120 - if len(c.ExtraHeaders) == 0 { 121 - c.ExtraHeaders = make(map[string][]byte) 122 - maps.Copy(c.ExtraHeaders, gc.ExtraHeaders) 123 - } 124 - 125 - if c.ChangeId == "" { 126 - if v, ok := gc.ExtraHeaders["change-id"]; ok { 127 - c.ChangeId = string(v) 128 - } 129 - } 130 - } 131 - 132 - func isEmptySignature(s object.Signature) bool { 133 - return s.Email == "" && s.Name == "" && s.When.IsZero() 134 - } 135 - 136 - // produce a verifiable payload from this commit's metadata 137 - func (c *Commit) Payload() string { 138 - author := bytes.NewBuffer([]byte{}) 139 - c.Author.Encode(author) 140 - 141 - committer := bytes.NewBuffer([]byte{}) 142 - c.Committer.Encode(committer) 143 - 144 - payload := strings.Builder{} 145 - 146 - fmt.Fprintf(&payload, "tree %s\n", c.Tree) 147 - 148 - if len(c.ParentHashes) > 0 { 149 - for _, p := range c.ParentHashes { 150 - fmt.Fprintf(&payload, "parent %s\n", p.String()) 151 - } 152 - } else { 153 - // present for backwards compatibility 154 - fmt.Fprintf(&payload, "parent %s\n", c.Parent) 155 - } 156 - 157 - fmt.Fprintf(&payload, "author %s\n", author.String()) 158 - fmt.Fprintf(&payload, "committer %s\n", committer.String()) 159 - 160 - if c.ChangeId != "" { 161 - fmt.Fprintf(&payload, "change-id %s\n", c.ChangeId) 162 - } else if v, ok := c.ExtraHeaders["change-id"]; ok { 163 - fmt.Fprintf(&payload, "change-id %s\n", string(v)) 164 - } 165 - 166 - fmt.Fprintf(&payload, "\n%s", c.Message) 167 - 168 - return payload.String() 169 - } 170 - 171 - var ( 172 - coAuthorRegex = regexp.MustCompile(`(?im)^Co-authored-by:\s*(.+?)\s*<([^>]+)>`) 173 - ) 174 - 175 - func (commit Commit) CoAuthors() []object.Signature { 176 - var coAuthors []object.Signature 177 - 178 - matches := coAuthorRegex.FindAllStringSubmatch(commit.Message, -1) 179 - 180 - for _, match := range matches { 181 - if len(match) >= 3 { 182 - name := strings.TrimSpace(match[1]) 183 - email := strings.TrimSpace(match[2]) 184 - 185 - coAuthors = append(coAuthors, object.Signature{ 186 - Name: name, 187 - Email: email, 188 - When: commit.Committer.When, 189 - }) 190 - } 191 - } 192 - 193 - return coAuthors 194 - }
NEW
types/diff.go
··· 2 2 3 3 import ( 4 4 "github.com/bluekeyes/go-gitdiff/gitdiff" 5 + "github.com/go-git/go-git/v5/plumbing/object" 5 6 ) 6 7 7 8 type DiffOpts struct { ··· 42 43 43 44 // A nicer git diff representation. 44 45 type NiceDiff struct { 45 - Commit Commit `json:"commit"` 46 - Stat struct { 46 + Commit struct { 47 + Message string `json:"message"` 48 + Author object.Signature `json:"author"` 49 + This string `json:"this"` 50 + Parent string `json:"parent"` 51 + PGPSignature string `json:"pgp_signature"` 52 + Committer object.Signature `json:"committer"` 53 + Tree string `json:"tree"` 54 + ChangedId string `json:"change_id"` 55 + } `json:"commit"` 56 + Stat struct { 47 57 FilesChanged int `json:"files_changed"` 48 58 Insertions int `json:"insertions"` 49 59 Deletions int `json:"deletions"`
NEW
types/repo.go
··· 8 8 ) 9 9 10 10 type RepoIndexResponse struct { 11 - IsEmpty bool `json:"is_empty"` 12 - Ref string `json:"ref,omitempty"` 13 - Readme string `json:"readme,omitempty"` 14 - ReadmeFileName string `json:"readme_file_name,omitempty"` 15 - Commits []Commit `json:"commits,omitempty"` 16 - Description string `json:"description,omitempty"` 17 - Files []NiceTree `json:"files,omitempty"` 18 - Branches []Branch `json:"branches,omitempty"` 19 - Tags []*TagReference `json:"tags,omitempty"` 20 - TotalCommits int `json:"total_commits,omitempty"` 11 + IsEmpty bool `json:"is_empty"` 12 + Ref string `json:"ref,omitempty"` 13 + Readme string `json:"readme,omitempty"` 14 + ReadmeFileName string `json:"readme_file_name,omitempty"` 15 + Commits []*object.Commit `json:"commits,omitempty"` 16 + Description string `json:"description,omitempty"` 17 + Files []NiceTree `json:"files,omitempty"` 18 + Branches []Branch `json:"branches,omitempty"` 19 + Tags []*TagReference `json:"tags,omitempty"` 20 + TotalCommits int `json:"total_commits,omitempty"` 21 21 } 22 22 23 23 type RepoLogResponse struct { 24 - Commits []Commit `json:"commits,omitempty"` 25 - Ref string `json:"ref,omitempty"` 26 - Description string `json:"description,omitempty"` 27 - Log bool `json:"log,omitempty"` 28 - Total int `json:"total,omitempty"` 29 - Page int `json:"page,omitempty"` 30 - PerPage int `json:"per_page,omitempty"` 24 + Commits []*object.Commit `json:"commits,omitempty"` 25 + Ref string `json:"ref,omitempty"` 26 + Description string `json:"description,omitempty"` 27 + Log bool `json:"log,omitempty"` 28 + Total int `json:"total,omitempty"` 29 + Page int `json:"page,omitempty"` 30 + PerPage int `json:"per_page,omitempty"` 31 31 } 32 32 33 33 type RepoCommitResponse struct {