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
···
import (
"log"
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/crypto"
···
return ""
}
-
func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.Commit) (VerifiedCommits, error) {
vcs := VerifiedCommits{}
didPubkeyCache := make(map[string][]models.PublicKey)
for _, commit := range ndCommits {
-
committerEmail := commit.Committer.Email
if did, exists := emailToDid[committerEmail]; exists {
// check if we've already fetched public keys for this did
pubKeys, ok := didPubkeyCache[did]
···
}
// try to verify with any associated pubkeys
-
payload := commit.Payload()
-
signature := commit.PGPSignature
for _, pk := range pubKeys {
-
if _, ok := crypto.VerifySignature([]byte(pk.Key), []byte(signature), []byte(payload)); ok {
fp, err := crypto.SSHFingerprint(pk.Key)
if err != nil {
log.Println("error computing ssh fingerprint:", err)
}
-
vc := verifiedCommit{fingerprint: fp, hash: commit.This}
vcs[vc] = struct{}{}
break
}
···
return vcs, nil
}
···
import (
"log"
+
"github.com/go-git/go-git/v5/plumbing/object"
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/crypto"
···
return ""
}
+
func GetVerifiedObjectCommits(e db.Execer, emailToDid map[string]string, commits []*object.Commit) (VerifiedCommits, error) {
+
ndCommits := []types.NiceDiff{}
+
for _, commit := range commits {
+
ndCommits = append(ndCommits, ObjectCommitToNiceDiff(commit))
+
}
+
return GetVerifiedCommits(e, emailToDid, ndCommits)
+
}
+
+
func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.NiceDiff) (VerifiedCommits, error) {
vcs := VerifiedCommits{}
didPubkeyCache := make(map[string][]models.PublicKey)
for _, commit := range ndCommits {
+
c := commit.Commit
+
+
committerEmail := c.Committer.Email
if did, exists := emailToDid[committerEmail]; exists {
// check if we've already fetched public keys for this did
pubKeys, ok := didPubkeyCache[did]
···
}
// try to verify with any associated pubkeys
for _, pk := range pubKeys {
+
if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok {
fp, err := crypto.SSHFingerprint(pk.Key)
if err != nil {
log.Println("error computing ssh fingerprint:", err)
}
+
vc := verifiedCommit{fingerprint: fp, hash: c.This}
vcs[vc] = struct{}{}
break
}
···
return vcs, nil
}
+
+
// ObjectCommitToNiceDiff is a compatibility function to convert a
+
// commit object into a NiceDiff structure.
+
func ObjectCommitToNiceDiff(c *object.Commit) types.NiceDiff {
+
var niceDiff types.NiceDiff
+
+
// set commit information
+
niceDiff.Commit.Message = c.Message
+
niceDiff.Commit.Author = c.Author
+
niceDiff.Commit.This = c.Hash.String()
+
niceDiff.Commit.Committer = c.Committer
+
niceDiff.Commit.Tree = c.TreeHash.String()
+
niceDiff.Commit.PGPSignature = c.PGPSignature
+
+
changeId, ok := c.ExtraHeaders["change-id"]
+
if ok {
+
niceDiff.Commit.ChangedId = string(changeId)
+
}
+
+
// set parent hash if available
+
if len(c.ParentHashes) > 0 {
+
niceDiff.Commit.Parent = c.ParentHashes[0].String()
+
}
+
+
// XXX: Stats and Diff fields are typically populated
+
// after fetching the actual diff information, which isn't
+
// directly available in the commit object itself.
+
+
return niceDiff
+
}
NEW
appview/pages/funcmap.go
···
}
return pairs, nil
},
-
"append": func(s []any, values ...any) []any {
s = append(s, values...)
return s
},
···
}
return pairs, nil
},
+
"append": func(s []string, values ...string) []string {
s = append(s, values...)
return s
},
NEW
appview/pages/pages.go
···
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/go-git/go-git/v5/plumbing"
)
//go:embed templates/* static legal
···
RepoInfo repoinfo.RepoInfo
Active string
TagMap map[string][]string
-
CommitsTrunc []types.Commit
TagsTrunc []*types.TagReference
BranchesTrunc []types.Branch
// ForkInfo *types.ForkInfo
···
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/go-git/go-git/v5/plumbing"
+
"github.com/go-git/go-git/v5/plumbing/object"
)
//go:embed templates/* static legal
···
RepoInfo repoinfo.RepoInfo
Active string
TagMap map[string][]string
+
CommitsTrunc []*object.Commit
TagsTrunc []*types.TagReference
BranchesTrunc []types.Branch
// ForkInfo *types.ForkInfo
NEW
appview/pages/templates/fragments/tinyAvatarList.html
···
-
{{ define "fragments/tinyAvatarList" }}
-
{{ $all := .all }}
-
{{ $classes := .classes }}
-
{{ $ps := take $all 5 }}
-
<div class="inline-flex items-center -space-x-3">
-
{{ $c := "z-50 z-40 z-30 z-20 z-10" }}
-
{{ range $i, $p := $ps }}
-
<img
-
src="{{ tinyAvatar . }}"
-
alt=""
-
class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0 {{ $classes }}"
-
/>
-
{{ end }}
-
-
{{ if gt (len $all) 5 }}
-
<span class="pl-4 text-gray-500 dark:text-gray-400 text-sm">
-
+{{ sub (len $all) 5 }}
-
</span>
-
{{ end }}
-
</div>
-
{{ end }}
-
···
NEW
appview/pages/templates/repo/commit.html
···
</div>
<div class="flex flex-wrap items-center space-x-2">
-
<p class="flex flex-wrap items-center gap-1 text-sm text-gray-500 dark:text-gray-300">
-
{{ template "attribution" . }}
<span class="px-1 select-none before:content-['\00B7']"></span>
-
{{ template "repo/fragments/time" $commit.Committer.When }}
<span class="px-1 select-none before:content-['\00B7']"></span>
<a href="/{{ $repo }}/commit/{{ $commit.This }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.This 0 8 }}</a>
···
</section>
{{end}}
-
-
{{ define "attribution" }}
-
{{ $commit := .Diff.Commit }}
-
{{ $showCommitter := true }}
-
{{ if eq $commit.Author.Email $commit.Committer.Email }}
-
{{ $showCommitter = false }}
-
{{ end }}
-
-
{{ if $showCommitter }}
-
authored by {{ template "attributedUser" (list $commit.Author.Email $commit.Author.Name $.EmailToDid) }}
-
{{ range $commit.CoAuthors }}
-
{{ template "attributedUser" (list .Email .Name $.EmailToDid) }}
-
{{ end }}
-
and committed by {{ template "attributedUser" (list $commit.Committer.Email $commit.Committer.Name $.EmailToDid) }}
-
{{ else }}
-
{{ template "attributedUser" (list $commit.Author.Email $commit.Author.Name $.EmailToDid )}}
-
{{ end }}
-
{{ end }}
-
-
{{ define "attributedUser" }}
-
{{ $email := index . 0 }}
-
{{ $name := index . 1 }}
-
{{ $map := index . 2 }}
-
{{ $did := index $map $email }}
-
-
{{ if $did }}
-
{{ template "user/fragments/picHandleLink" $did }}
-
{{ else }}
-
<a href="mailto:{{ $email }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ $name }}</a>
-
{{ end }}
-
{{ end }}
{{ define "topbarLayout" }}
<header class="col-span-full" style="z-index: 20;">
···
</div>
<div class="flex flex-wrap items-center space-x-2">
+
<p class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-300">
+
{{ $did := index $.EmailToDid $commit.Author.Email }}
+
+
{{ if $did }}
+
{{ template "user/fragments/picHandleLink" $did }}
+
{{ else }}
+
<a href="mailto:{{ $commit.Author.Email }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ $commit.Author.Name }}</a>
+
{{ end }}
<span class="px-1 select-none before:content-['\00B7']"></span>
+
{{ template "repo/fragments/time" $commit.Author.When }}
<span class="px-1 select-none before:content-['\00B7']"></span>
<a href="/{{ $repo }}/commit/{{ $commit.This }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.This 0 8 }}</a>
···
</section>
{{end}}
{{ define "topbarLayout" }}
<header class="col-span-full" style="z-index: 20;">
NEW
appview/pages/templates/repo/fragments/participants.html
···
<span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span>
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span>
</div>
-
{{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "w-8 h-8") }}
</div>
{{ end }}
···
<span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span>
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span>
</div>
+
<div class="flex items-center -space-x-3 mt-2">
+
{{ $c := "z-50 z-40 z-30 z-20 z-10" }}
+
{{ range $i, $p := $ps }}
+
<img
+
src="{{ tinyAvatar . }}"
+
alt=""
+
class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0"
+
/>
+
{{ end }}
+
+
{{ if gt (len $all) 5 }}
+
<span class="pl-4 text-gray-500 dark:text-gray-400 text-sm">
+
+{{ sub (len $all) 5 }}
+
</span>
+
{{ end }}
+
</div>
</div>
{{ end }}
NEW
appview/pages/templates/repo/index.html
···
{{ end }}
<div class="flex items-center justify-between pb-5">
{{ block "branchSelector" . }}{{ end }}
-
<div class="flex md:hidden items-center gap-3">
<a href="/{{ .RepoInfo.FullName }}/commits/{{ .Ref | urlquery }}" class="inline-flex items-center text-sm gap-1 font-bold">
{{ i "git-commit-horizontal" "w-4" "h-4" }} {{ .TotalCommits }}
</a>
···
{{ define "branchSelector" }}
<div class="flex gap-2 items-center justify-between w-full">
-
<div class="flex gap-2 items-stretch">
<select
onchange="window.location.href = '/{{ .RepoInfo.FullName }}/tree/' + encodeURIComponent(this.value)"
class="p-1 border max-w-32 border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"
···
<span
class="mx-1 before:content-['·'] before:select-none"
></span>
-
{{ template "attribution" (list . $.EmailToDid) }}
<div class="inline-block px-1 select-none after:content-['·']"></div>
{{ template "repo/fragments/time" .Committer.When }}
···
{{ end }}
</div>
</div>
-
{{ end }}
-
-
{{ define "attribution" }}
-
{{ $commit := index . 0 }}
-
{{ $map := index . 1 }}
-
<span class="flex items-center">
-
{{ $author := index $map $commit.Author.Email }}
-
{{ $coauthors := $commit.CoAuthors }}
-
{{ $all := list }}
-
-
{{ if $author }}
-
{{ $all = append $all $author }}
-
{{ end }}
-
{{ range $coauthors }}
-
{{ $co := index $map .Email }}
-
{{ if $co }}
-
{{ $all = append $all $co }}
-
{{ end }}
-
{{ end }}
-
-
{{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "size-6") }}
-
<a href="{{ if $author }}/{{ $author }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}"
-
class="no-underline hover:underline">
-
{{ if $author }}{{ resolve $author }}{{ else }}{{ $commit.Author.Name }}{{ end }}
-
{{ if $coauthors }} +{{ length $coauthors }}{{ end }}
-
</a>
-
</span>
{{ end }}
{{ define "branchList" }}
···
{{ end }}
<div class="flex items-center justify-between pb-5">
{{ block "branchSelector" . }}{{ end }}
+
<div class="flex md:hidden items-center gap-2">
<a href="/{{ .RepoInfo.FullName }}/commits/{{ .Ref | urlquery }}" class="inline-flex items-center text-sm gap-1 font-bold">
{{ i "git-commit-horizontal" "w-4" "h-4" }} {{ .TotalCommits }}
</a>
···
{{ define "branchSelector" }}
<div class="flex gap-2 items-center justify-between w-full">
+
<div class="flex gap-2 items-center">
<select
onchange="window.location.href = '/{{ .RepoInfo.FullName }}/tree/' + encodeURIComponent(this.value)"
class="p-1 border max-w-32 border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"
···
<span
class="mx-1 before:content-['·'] before:select-none"
></span>
+
<span>
+
{{ $did := index $.EmailToDid .Author.Email }}
+
<a href="{{ if $did }}/{{ resolve $did }}{{ else }}mailto:{{ .Author.Email }}{{ end }}"
+
class="text-gray-500 dark:text-gray-400 no-underline hover:underline"
+
>{{ if $did }}{{ template "user/fragments/picHandleLink" $did }}{{ else }}{{ .Author.Name }}{{ end }}</a>
+
</span>
<div class="inline-block px-1 select-none after:content-['·']"></div>
{{ template "repo/fragments/time" .Committer.When }}
···
{{ end }}
</div>
</div>
{{ end }}
{{ define "branchList" }}
NEW
appview/pages/templates/repo/log.html
···
<div class="hidden md:flex md:flex-col divide-y divide-gray-200 dark:divide-gray-700">
{{ $grid := "grid grid-cols-14 gap-4" }}
<div class="{{ $grid }}">
-
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-3">Author</div>
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-3">Commit</div>
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-6">Message</div>
<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>
</div>
{{ range $index, $commit := .Commits }}
{{ $messageParts := splitN $commit.Message "\n\n" 2 }}
<div class="{{ $grid }} py-3">
-
<div class="align-top col-span-3">
-
{{ template "attribution" (list $commit $.EmailToDid) }}
</div>
<div class="align-top font-mono flex items-start col-span-3">
{{ $verified := $.VerifiedCommits.IsVerified $commit.Hash.String }}
···
<div class="align-top col-span-6">
<div>
<a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" class="dark:text-white no-underline hover:underline">{{ index $messageParts 0 }}</a>
-
{{ if gt (len $messageParts) 1 }}
<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>
{{ end }}
···
</span>
{{ end }}
{{ end }}
-
-
<!-- ci status -->
-
<span class="text-xs">
-
{{ $pipeline := index $.Pipelines .Hash.String }}
-
{{ if and $pipeline (gt (len $pipeline.Statuses) 0) }}
-
{{ template "repo/pipelines/fragments/pipelineSymbolLong" (dict "Pipeline" $pipeline "RepoInfo" $.RepoInfo) }}
-
{{ end }}
-
</span>
</div>
{{ if gt (len $messageParts) 1 }}
<p class="hidden mt-1 text-sm text-gray-600 dark:text-gray-400">{{ nl2br (index $messageParts 1) }}</p>
{{ end }}
</div>
<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>
</div>
···
</a>
</span>
<span class="mx-2 before:content-['·'] before:select-none"></span>
-
{{ template "attribution" (list $commit $.EmailToDid) }}
<div class="inline-block px-1 select-none after:content-['·']"></div>
<span>{{ template "repo/fragments/shortTime" $commit.Committer.When }}</span>
···
</div>
</section>
-
{{ end }}
-
-
{{ define "attribution" }}
-
{{ $commit := index . 0 }}
-
{{ $map := index . 1 }}
-
<span class="flex items-center gap-1">
-
{{ $author := index $map $commit.Author.Email }}
-
{{ $coauthors := $commit.CoAuthors }}
-
{{ $all := list }}
-
-
{{ if $author }}
-
{{ $all = append $all $author }}
-
{{ end }}
-
{{ range $coauthors }}
-
{{ $co := index $map .Email }}
-
{{ if $co }}
-
{{ $all = append $all $co }}
-
{{ end }}
-
{{ end }}
-
-
{{ template "fragments/tinyAvatarList" (dict "all" $all "classes" "size-6") }}
-
<a href="{{ if $author }}/{{ $author }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}"
-
class="no-underline hover:underline">
-
{{ if $author }}{{ resolve $author }}{{ else }}{{ $commit.Author.Name }}{{ end }}
-
{{ if $coauthors }} +{{ length $coauthors }}{{ end }}
-
</a>
-
</span>
{{ end }}
{{ define "repoAfter" }}
···
<div class="hidden md:flex md:flex-col divide-y divide-gray-200 dark:divide-gray-700">
{{ $grid := "grid grid-cols-14 gap-4" }}
<div class="{{ $grid }}">
+
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-2">Author</div>
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-3">Commit</div>
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-6">Message</div>
+
<div class="py-2 text-sm text-left text-gray-700 dark:text-gray-300 uppercase font-bold col-span-1"></div>
<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>
</div>
{{ range $index, $commit := .Commits }}
{{ $messageParts := splitN $commit.Message "\n\n" 2 }}
<div class="{{ $grid }} py-3">
+
<div class="align-top truncate col-span-2">
+
{{ $did := index $.EmailToDid $commit.Author.Email }}
+
{{ if $did }}
+
{{ template "user/fragments/picHandleLink" $did }}
+
{{ else }}
+
<a href="mailto:{{ $commit.Author.Email }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $commit.Author.Name }}</a>
+
{{ end }}
</div>
<div class="align-top font-mono flex items-start col-span-3">
{{ $verified := $.VerifiedCommits.IsVerified $commit.Hash.String }}
···
<div class="align-top col-span-6">
<div>
<a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" class="dark:text-white no-underline hover:underline">{{ index $messageParts 0 }}</a>
{{ if gt (len $messageParts) 1 }}
<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>
{{ end }}
···
</span>
{{ end }}
{{ end }}
</div>
{{ if gt (len $messageParts) 1 }}
<p class="hidden mt-1 text-sm text-gray-600 dark:text-gray-400">{{ nl2br (index $messageParts 1) }}</p>
{{ end }}
+
</div>
+
<div class="align-top col-span-1">
+
<!-- ci status -->
+
{{ $pipeline := index $.Pipelines .Hash.String }}
+
{{ if and $pipeline (gt (len $pipeline.Statuses) 0) }}
+
{{ template "repo/pipelines/fragments/pipelineSymbolLong" (dict "Pipeline" $pipeline "RepoInfo" $.RepoInfo) }}
+
{{ end }}
</div>
<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>
</div>
···
</a>
</span>
<span class="mx-2 before:content-['·'] before:select-none"></span>
+
<span>
+
{{ $did := index $.EmailToDid $commit.Author.Email }}
+
<a href="{{ if $did }}/{{ $did }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}"
+
class="text-gray-500 dark:text-gray-400 no-underline hover:underline">
+
{{ if $did }}{{ template "user/fragments/picHandleLink" $did }}{{ else }}{{ $commit.Author.Name }}{{ end }}
+
</a>
+
</span>
<div class="inline-block px-1 select-none after:content-['·']"></div>
<span>{{ template "repo/fragments/shortTime" $commit.Committer.When }}</span>
···
</div>
</section>
{{ end }}
{{ define "repoAfter" }}
NEW
appview/repo/index.go
···
l.Error("failed to get email to did map", "err", err)
}
-
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, commitsTrunc)
if err != nil {
l.Error("failed to GetVerifiedObjectCommits", "err", err)
}
···
l.Error("failed to get email to did map", "err", err)
}
+
vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, commitsTrunc)
if err != nil {
l.Error("failed to GetVerifiedObjectCommits", "err", err)
}
NEW
appview/repo/log.go
···
l.Error("failed to fetch email to did mapping", "err", err)
}
-
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, xrpcResp.Commits)
if err != nil {
l.Error("failed to GetVerifiedObjectCommits", "err", err)
}
···
l.Error("failed to get email to did mapping", "err", err)
}
-
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit})
if err != nil {
l.Error("failed to GetVerifiedCommits", "err", err)
}
···
l.Error("failed to fetch email to did mapping", "err", err)
}
+
vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits)
if err != nil {
l.Error("failed to GetVerifiedObjectCommits", "err", err)
}
···
l.Error("failed to get email to did mapping", "err", err)
}
+
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.NiceDiff{*result.Diff})
if err != nil {
l.Error("failed to GetVerifiedCommits", "err", err)
}
NEW
appview/repo/repo_util.go
···
package repo
import (
-
"maps"
"slices"
"sort"
"strings"
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/types"
)
func sortFiles(files []types.NiceTree) {
···
})
}
-
func uniqueEmails(commits []types.Commit) []string {
emails := make(map[string]struct{})
for _, commit := range commits {
-
emails[commit.Author.Email] = struct{}{}
-
emails[commit.Committer.Email] = struct{}{}
-
for _, c := range commit.CoAuthors() {
-
emails[c.Email] = struct{}{}
}
}
-
-
// delete empty emails if any, from the set
-
delete(emails, "")
-
-
return slices.Collect(maps.Keys(emails))
}
func balanceIndexItems(commitCount, branchCount, tagCount, fileCount int) (commitsTrunc int, branchesTrunc int, tagsTrunc int) {
···
package repo
import (
"slices"
"sort"
"strings"
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/types"
+
+
"github.com/go-git/go-git/v5/plumbing/object"
)
func sortFiles(files []types.NiceTree) {
···
})
}
+
func uniqueEmails(commits []*object.Commit) []string {
emails := make(map[string]struct{})
for _, commit := range commits {
+
if commit.Author.Email != "" {
+
emails[commit.Author.Email] = struct{}{}
+
}
+
if commit.Committer.Email != "" {
+
emails[commit.Committer.Email] = struct{}{}
}
}
+
var uniqueEmails []string
+
for email := range emails {
+
uniqueEmails = append(uniqueEmails, email)
+
}
+
return uniqueEmails
}
func balanceIndexItems(commitCount, branchCount, tagCount, fileCount int) (commitsTrunc int, branchesTrunc int, tagsTrunc int) {
NEW
crypto/verify.go
···
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/hiddeco/sshsig"
"golang.org/x/crypto/ssh"
)
func VerifySignature(pubKey, signature, payload []byte) (error, bool) {
···
// multiple algorithms but sha-512 is most secure, and git's ssh signing defaults
// to sha-512 for all key types anyway.
err = sshsig.Verify(buf, sig, pub, sshsig.HashSHA512, "git")
-
return err, err == nil
}
// SSHFingerprint computes the fingerprint of the supplied ssh pubkey.
···
"crypto/sha256"
"encoding/base64"
"fmt"
+
"strings"
"github.com/hiddeco/sshsig"
"golang.org/x/crypto/ssh"
+
"tangled.org/core/types"
)
func VerifySignature(pubKey, signature, payload []byte) (error, bool) {
···
// multiple algorithms but sha-512 is most secure, and git's ssh signing defaults
// to sha-512 for all key types anyway.
err = sshsig.Verify(buf, sig, pub, sshsig.HashSHA512, "git")
+
return err, err == nil
+
}
+
// VerifyCommitSignature reconstructs the payload used to sign a commit. This is
+
// essentially the git cat-file output but without the gpgsig header.
+
//
+
// Caveats: signature verification will fail on commits with more than one parent,
+
// i.e. merge commits, because types.NiceDiff doesn't carry more than one Parent field
+
// and we are unable to reconstruct the payload correctly.
+
//
+
// Ideally this should directly operate on an *object.Commit.
+
func VerifyCommitSignature(pubKey string, commit types.NiceDiff) (error, bool) {
+
signature := commit.Commit.PGPSignature
+
+
author := bytes.NewBuffer([]byte{})
+
committer := bytes.NewBuffer([]byte{})
+
commit.Commit.Author.Encode(author)
+
commit.Commit.Committer.Encode(committer)
+
+
payload := strings.Builder{}
+
+
fmt.Fprintf(&payload, "tree %s\n", commit.Commit.Tree)
+
if commit.Commit.Parent != "" {
+
fmt.Fprintf(&payload, "parent %s\n", commit.Commit.Parent)
+
}
+
fmt.Fprintf(&payload, "author %s\n", author.String())
+
fmt.Fprintf(&payload, "committer %s\n", committer.String())
+
if commit.Commit.ChangedId != "" {
+
fmt.Fprintf(&payload, "change-id %s\n", commit.Commit.ChangedId)
+
}
+
fmt.Fprintf(&payload, "\n%s", commit.Commit.Message)
+
+
return VerifySignature([]byte(pubKey), []byte(signature), []byte(payload.String()))
}
// SSHFingerprint computes the fingerprint of the supplied ssh pubkey.
NEW
go.mod
···
github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v3 v3.3.3
github.com/whyrusleeping/cbor-gen v0.3.1
-
github.com/wyatt915/goldmark-treeblood v0.0.1
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab
···
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-
github.com/wyatt915/treeblood v0.1.16 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
···
github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v3 v3.3.3
github.com/whyrusleeping/cbor-gen v0.3.1
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab
···
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
NEW
go.sum
···
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
-
github.com/wyatt915/goldmark-treeblood v0.0.1 h1:6vLJcjFrHgE4ASu2ga4hqIQmbvQLU37v53jlHZ3pqDs=
-
github.com/wyatt915/goldmark-treeblood v0.0.1/go.mod h1:SmcJp5EBaV17rroNlgNQFydYwy0+fv85CUr/ZaCz208=
-
github.com/wyatt915/treeblood v0.1.16 h1:byxNbWZhnPDxdTp7W5kQhCeaY8RBVmojTFz1tEHgg8Y=
-
github.com/wyatt915/treeblood v0.1.16/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
···
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
NEW
knotserver/git/diff.go
···
nd.Diff = append(nd.Diff, ndiff)
}
-
nd.Commit.FromGoGitCommit(c)
return &nd, nil
}
···
nd.Diff = append(nd.Diff, ndiff)
}
+
nd.Stat.FilesChanged = len(diffs)
+
nd.Commit.This = c.Hash.String()
+
nd.Commit.PGPSignature = c.PGPSignature
+
nd.Commit.Committer = c.Committer
+
nd.Commit.Tree = c.TreeHash.String()
+
+
if parent.Hash.IsZero() {
+
nd.Commit.Parent = ""
+
} else {
+
nd.Commit.Parent = parent.Hash.String()
+
}
+
nd.Commit.Author = c.Author
+
nd.Commit.Message = c.Message
+
+
if v, ok := c.ExtraHeaders["change-id"]; ok {
+
nd.Commit.ChangedId = string(v)
+
}
return &nd, nil
}
NEW
knotserver/xrpc/repo_log.go
···
return
}
-
tcommits := make([]types.Commit, len(commits))
-
for i, c := range commits {
-
tcommits[i].FromGoGitCommit(c)
-
}
-
// Create response using existing types.RepoLogResponse
response := types.RepoLogResponse{
-
Commits: tcommits,
Ref: ref,
Page: (offset / limit) + 1,
PerPage: limit,
···
return
}
// Create response using existing types.RepoLogResponse
response := types.RepoLogResponse{
+
Commits: commits,
Ref: ref,
Page: (offset / limit) + 1,
PerPage: limit,
NEW
nix/gomod2nix.toml
···
[mod."github.com/davecgh/go-spew"]
version = "v1.1.2-0.20180830191138-d8f796af33cc"
hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc="
-
[mod."github.com/decred/dcrd/dcrec/secp256k1/v4"]
-
version = "v4.4.0"
-
hash = "sha256-qrhEIwhDll3cxoVpMbm1NQ9/HTI42S7ms8Buzlo5HCg="
[mod."github.com/dgraph-io/ristretto"]
version = "v0.2.0"
hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw="
···
[mod."github.com/klauspost/cpuid/v2"]
version = "v2.3.0"
hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc="
-
[mod."github.com/lestrrat-go/blackmagic"]
-
version = "v1.0.4"
-
hash = "sha256-HmWOpwoPDNMwLdOi7onNn3Sb+ZsAa3Ai3gVBbXmQ0e8="
-
[mod."github.com/lestrrat-go/httpcc"]
-
version = "v1.0.1"
-
hash = "sha256-SMRSwJpqDIs/xL0l2e8vP0W65qtCHX2wigcOeqPJmos="
-
[mod."github.com/lestrrat-go/httprc"]
-
version = "v1.0.6"
-
hash = "sha256-mfZzePEhrmyyu/avEBd2MsDXyto8dq5+fyu5lA8GUWM="
-
[mod."github.com/lestrrat-go/iter"]
-
version = "v1.0.2"
-
hash = "sha256-30tErRf7Qu/NOAt1YURXY/XJSA6sCr6hYQfO8QqHrtw="
-
[mod."github.com/lestrrat-go/jwx/v2"]
-
version = "v2.1.6"
-
hash = "sha256-0LszXRZIba+X8AOrs3T4uanAUafBdlVB8/MpUNEFpbc="
-
[mod."github.com/lestrrat-go/option"]
-
version = "v1.0.1"
-
hash = "sha256-jVcIYYVsxElIS/l2akEw32vdEPR8+anR6oeT1FoYULI="
[mod."github.com/lucasb-eyer/go-colorful"]
version = "v1.2.0"
hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE="
···
[mod."github.com/ryanuber/go-glob"]
version = "v1.0.0"
hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY="
-
[mod."github.com/segmentio/asm"]
-
version = "v1.2.0"
-
hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs="
[mod."github.com/sergi/go-diff"]
version = "v1.1.0"
hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY="
···
[mod."github.com/whyrusleeping/cbor-gen"]
version = "v0.3.1"
hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc="
-
[mod."github.com/wyatt915/goldmark-treeblood"]
-
version = "v0.0.1"
-
hash = "sha256-hAVFaktO02MiiqZFffr8ZlvFEfwxw4Y84OZ2t7e5G7g="
-
[mod."github.com/wyatt915/treeblood"]
-
version = "v0.1.16"
-
hash = "sha256-T68sa+iVx0qY7dDjXEAJvRWQEGXYIpUsf9tcWwO1tIw="
[mod."github.com/xo/terminfo"]
version = "v0.0.0-20220910002029-abceb7e1c41e"
hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU="
···
[mod."github.com/davecgh/go-spew"]
version = "v1.1.2-0.20180830191138-d8f796af33cc"
hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc="
[mod."github.com/dgraph-io/ristretto"]
version = "v0.2.0"
hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw="
···
[mod."github.com/klauspost/cpuid/v2"]
version = "v2.3.0"
hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc="
[mod."github.com/lucasb-eyer/go-colorful"]
version = "v1.2.0"
hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE="
···
[mod."github.com/ryanuber/go-glob"]
version = "v1.0.0"
hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY="
[mod."github.com/sergi/go-diff"]
version = "v1.1.0"
hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY="
···
[mod."github.com/whyrusleeping/cbor-gen"]
version = "v0.3.1"
hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc="
[mod."github.com/xo/terminfo"]
version = "v0.0.0-20220910002029-abceb7e1c41e"
hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU="
NEW
patchutil/patchutil.go
···
}
nd := types.NiceDiff{}
for _, d := range diffs {
ndiff := types.Diff{}
···
}
nd := types.NiceDiff{}
+
nd.Commit.Parent = targetBranch
for _, d := range diffs {
ndiff := types.Diff{}
NEW
types/commit.go
···
-
package types
-
-
import (
-
"bytes"
-
"encoding/json"
-
"fmt"
-
"maps"
-
"regexp"
-
"strings"
-
-
"github.com/go-git/go-git/v5/plumbing"
-
"github.com/go-git/go-git/v5/plumbing/object"
-
)
-
-
type Commit struct {
-
// hash of the commit object.
-
Hash plumbing.Hash `json:"hash,omitempty"`
-
-
// author is the original author of the commit.
-
Author object.Signature `json:"author"`
-
-
// committer is the one performing the commit, might be different from author.
-
Committer object.Signature `json:"committer"`
-
-
// message is the commit message, contains arbitrary text.
-
Message string `json:"message"`
-
-
// treehash is the hash of the root tree of the commit.
-
Tree string `json:"tree"`
-
-
// parents are the hashes of the parent commits of the commit.
-
ParentHashes []plumbing.Hash `json:"parent_hashes,omitempty"`
-
-
// pgpsignature is the pgp signature of the commit.
-
PGPSignature string `json:"pgp_signature,omitempty"`
-
-
// mergetag is the embedded tag object when a merge commit is created by
-
// merging a signed tag.
-
MergeTag string `json:"merge_tag,omitempty"`
-
-
// changeid is a unique identifier for the change (e.g., gerrit change-id).
-
ChangeId string `json:"change_id,omitempty"`
-
-
// extraheaders contains additional headers not captured by other fields.
-
ExtraHeaders map[string][]byte `json:"extra_headers,omitempty"`
-
-
// deprecated: kept for backwards compatibility with old json format.
-
This string `json:"this,omitempty"`
-
-
// deprecated: kept for backwards compatibility with old json format.
-
Parent string `json:"parent,omitempty"`
-
}
-
-
// types.Commit is an unify two commit structs:
-
// - git.object.Commit from
-
// - types.NiceDiff.commit
-
//
-
// to do this in backwards compatible fashion, we define the base struct
-
// to use the same fields as NiceDiff.Commit, and then we also unmarshal
-
// the struct fields from go-git structs, this custom unmarshal makes sense
-
// of both representations and unifies them to have maximal data in either
-
// form.
-
func (c *Commit) UnmarshalJSON(data []byte) error {
-
type Alias Commit
-
-
aux := &struct {
-
*object.Commit
-
*Alias
-
}{
-
Alias: (*Alias)(c),
-
}
-
-
if err := json.Unmarshal(data, aux); err != nil {
-
return err
-
}
-
-
c.FromGoGitCommit(aux.Commit)
-
-
return nil
-
}
-
-
// fill in as much of Commit as possible from the given go-git commit
-
func (c *Commit) FromGoGitCommit(gc *object.Commit) {
-
if gc == nil {
-
return
-
}
-
-
if c.Hash.IsZero() {
-
c.Hash = gc.Hash
-
}
-
if c.This == "" {
-
c.This = gc.Hash.String()
-
}
-
if isEmptySignature(c.Author) {
-
c.Author = gc.Author
-
}
-
if isEmptySignature(c.Committer) {
-
c.Committer = gc.Committer
-
}
-
if c.Message == "" {
-
c.Message = gc.Message
-
}
-
if c.Tree == "" {
-
c.Tree = gc.TreeHash.String()
-
}
-
if c.PGPSignature == "" {
-
c.PGPSignature = gc.PGPSignature
-
}
-
if c.MergeTag == "" {
-
c.MergeTag = gc.MergeTag
-
}
-
-
if len(c.ParentHashes) == 0 {
-
c.ParentHashes = gc.ParentHashes
-
}
-
if c.Parent == "" && len(gc.ParentHashes) > 0 {
-
c.Parent = gc.ParentHashes[0].String()
-
}
-
-
if len(c.ExtraHeaders) == 0 {
-
c.ExtraHeaders = make(map[string][]byte)
-
maps.Copy(c.ExtraHeaders, gc.ExtraHeaders)
-
}
-
-
if c.ChangeId == "" {
-
if v, ok := gc.ExtraHeaders["change-id"]; ok {
-
c.ChangeId = string(v)
-
}
-
}
-
}
-
-
func isEmptySignature(s object.Signature) bool {
-
return s.Email == "" && s.Name == "" && s.When.IsZero()
-
}
-
-
// produce a verifiable payload from this commit's metadata
-
func (c *Commit) Payload() string {
-
author := bytes.NewBuffer([]byte{})
-
c.Author.Encode(author)
-
-
committer := bytes.NewBuffer([]byte{})
-
c.Committer.Encode(committer)
-
-
payload := strings.Builder{}
-
-
fmt.Fprintf(&payload, "tree %s\n", c.Tree)
-
-
if len(c.ParentHashes) > 0 {
-
for _, p := range c.ParentHashes {
-
fmt.Fprintf(&payload, "parent %s\n", p.String())
-
}
-
} else {
-
// present for backwards compatibility
-
fmt.Fprintf(&payload, "parent %s\n", c.Parent)
-
}
-
-
fmt.Fprintf(&payload, "author %s\n", author.String())
-
fmt.Fprintf(&payload, "committer %s\n", committer.String())
-
-
if c.ChangeId != "" {
-
fmt.Fprintf(&payload, "change-id %s\n", c.ChangeId)
-
} else if v, ok := c.ExtraHeaders["change-id"]; ok {
-
fmt.Fprintf(&payload, "change-id %s\n", string(v))
-
}
-
-
fmt.Fprintf(&payload, "\n%s", c.Message)
-
-
return payload.String()
-
}
-
-
var (
-
coAuthorRegex = regexp.MustCompile(`(?im)^Co-authored-by:\s*(.+?)\s*<([^>]+)>`)
-
)
-
-
func (commit Commit) CoAuthors() []object.Signature {
-
var coAuthors []object.Signature
-
-
matches := coAuthorRegex.FindAllStringSubmatch(commit.Message, -1)
-
-
for _, match := range matches {
-
if len(match) >= 3 {
-
name := strings.TrimSpace(match[1])
-
email := strings.TrimSpace(match[2])
-
-
coAuthors = append(coAuthors, object.Signature{
-
Name: name,
-
Email: email,
-
When: commit.Committer.When,
-
})
-
}
-
}
-
-
return coAuthors
-
}
···
NEW
types/diff.go
···
import (
"github.com/bluekeyes/go-gitdiff/gitdiff"
)
type DiffOpts struct {
···
// A nicer git diff representation.
type NiceDiff struct {
-
Commit Commit `json:"commit"`
-
Stat struct {
FilesChanged int `json:"files_changed"`
Insertions int `json:"insertions"`
Deletions int `json:"deletions"`
···
import (
"github.com/bluekeyes/go-gitdiff/gitdiff"
+
"github.com/go-git/go-git/v5/plumbing/object"
)
type DiffOpts struct {
···
// A nicer git diff representation.
type NiceDiff struct {
+
Commit struct {
+
Message string `json:"message"`
+
Author object.Signature `json:"author"`
+
This string `json:"this"`
+
Parent string `json:"parent"`
+
PGPSignature string `json:"pgp_signature"`
+
Committer object.Signature `json:"committer"`
+
Tree string `json:"tree"`
+
ChangedId string `json:"change_id"`
+
} `json:"commit"`
+
Stat struct {
FilesChanged int `json:"files_changed"`
Insertions int `json:"insertions"`
Deletions int `json:"deletions"`
NEW
types/repo.go
···
)
type RepoIndexResponse struct {
-
IsEmpty bool `json:"is_empty"`
-
Ref string `json:"ref,omitempty"`
-
Readme string `json:"readme,omitempty"`
-
ReadmeFileName string `json:"readme_file_name,omitempty"`
-
Commits []Commit `json:"commits,omitempty"`
-
Description string `json:"description,omitempty"`
-
Files []NiceTree `json:"files,omitempty"`
-
Branches []Branch `json:"branches,omitempty"`
-
Tags []*TagReference `json:"tags,omitempty"`
-
TotalCommits int `json:"total_commits,omitempty"`
}
type RepoLogResponse struct {
-
Commits []Commit `json:"commits,omitempty"`
-
Ref string `json:"ref,omitempty"`
-
Description string `json:"description,omitempty"`
-
Log bool `json:"log,omitempty"`
-
Total int `json:"total,omitempty"`
-
Page int `json:"page,omitempty"`
-
PerPage int `json:"per_page,omitempty"`
}
type RepoCommitResponse struct {
···
)
type RepoIndexResponse struct {
+
IsEmpty bool `json:"is_empty"`
+
Ref string `json:"ref,omitempty"`
+
Readme string `json:"readme,omitempty"`
+
ReadmeFileName string `json:"readme_file_name,omitempty"`
+
Commits []*object.Commit `json:"commits,omitempty"`
+
Description string `json:"description,omitempty"`
+
Files []NiceTree `json:"files,omitempty"`
+
Branches []Branch `json:"branches,omitempty"`
+
Tags []*TagReference `json:"tags,omitempty"`
+
TotalCommits int `json:"total_commits,omitempty"`
}
type RepoLogResponse struct {
+
Commits []*object.Commit `json:"commits,omitempty"`
+
Ref string `json:"ref,omitempty"`
+
Description string `json:"description,omitempty"`
+
Log bool `json:"log,omitempty"`
+
Total int `json:"total,omitempty"`
+
Page int `json:"page,omitempty"`
+
PerPage int `json:"per_page,omitempty"`
}
type RepoCommitResponse struct {