1package repo
2
3import (
4 "context"
5 "crypto/rand"
6 "fmt"
7 "log"
8 "math/big"
9
10 "github.com/go-git/go-git/v5/plumbing/object"
11 "tangled.sh/tangled.sh/core/appview/db"
12 "tangled.sh/tangled.sh/core/crypto"
13 "tangled.sh/tangled.sh/core/types"
14)
15
16func uniqueEmails(commits []*object.Commit) []string {
17 emails := make(map[string]struct{})
18 for _, commit := range commits {
19 if commit.Author.Email != "" {
20 emails[commit.Author.Email] = struct{}{}
21 }
22 if commit.Committer.Email != "" {
23 emails[commit.Committer.Email] = struct{}{}
24 }
25 }
26 var uniqueEmails []string
27 for email := range emails {
28 uniqueEmails = append(uniqueEmails, email)
29 }
30 return uniqueEmails
31}
32
33func balanceIndexItems(commitCount, branchCount, tagCount, fileCount int) (commitsTrunc int, branchesTrunc int, tagsTrunc int) {
34 if commitCount == 0 && tagCount == 0 && branchCount == 0 {
35 return
36 }
37
38 // typically 1 item on right side = 2 files in height
39 availableSpace := fileCount / 2
40
41 // clamp tagcount
42 if tagCount > 0 {
43 tagsTrunc = 1
44 availableSpace -= 1 // an extra subtracted for headers etc.
45 }
46
47 // clamp branchcount
48 if branchCount > 0 {
49 branchesTrunc = min(max(branchCount, 1), 3)
50 availableSpace -= branchesTrunc // an extra subtracted for headers etc.
51 }
52
53 // show
54 if commitCount > 0 {
55 commitsTrunc = max(availableSpace, 3)
56 }
57
58 return
59}
60
61// emailToDidOrHandle takes an emailToDidMap from db.GetEmailToDid
62// and resolves all dids to handles and returns a new map[string]string
63func emailToDidOrHandle(r *Repo, emailToDidMap map[string]string) map[string]string {
64 if emailToDidMap == nil {
65 return nil
66 }
67
68 var dids []string
69 for _, v := range emailToDidMap {
70 dids = append(dids, v)
71 }
72 resolvedIdents := r.idResolver.ResolveIdents(context.Background(), dids)
73
74 didHandleMap := make(map[string]string)
75 for _, identity := range resolvedIdents {
76 if !identity.Handle.IsInvalidHandle() {
77 didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
78 } else {
79 didHandleMap[identity.DID.String()] = identity.DID.String()
80 }
81 }
82
83 // Create map of email to didOrHandle for commit display
84 emailToDidOrHandle := make(map[string]string)
85 for email, did := range emailToDidMap {
86 if didOrHandle, ok := didHandleMap[did]; ok {
87 emailToDidOrHandle[email] = didOrHandle
88 }
89 }
90
91 return emailToDidOrHandle
92}
93
94func verifiedObjectCommits(r *Repo, emailToDid map[string]string, commits []*object.Commit) (map[string]bool, error) {
95 ndCommits := []types.NiceDiff{}
96 for _, commit := range commits {
97 ndCommits = append(ndCommits, types.ObjectCommitToNiceDiff(commit))
98 }
99 return verifiedCommits(r, emailToDid, ndCommits)
100}
101
102func verifiedCommits(r *Repo, emailToDid map[string]string, ndCommits []types.NiceDiff) (map[string]bool, error) {
103 hashToVerified := make(map[string]bool)
104
105 didPubkeyCache := make(map[string][]db.PublicKey)
106
107 for _, commit := range ndCommits {
108 c := commit.Commit
109
110 committerEmail := c.Committer.Email
111 if did, exists := emailToDid[committerEmail]; exists {
112 // check if we've already fetched public keys for this did
113 pubKeys, ok := didPubkeyCache[did]
114 if !ok {
115 // fetch and cache public keys
116 keys, err := db.GetPublicKeysForDid(r.db, did)
117 if err != nil {
118 log.Printf("failed to fetch pubkey for %s: %v", committerEmail, err)
119 continue
120 }
121 pubKeys = keys
122 didPubkeyCache[did] = pubKeys
123 }
124
125 verified := false
126
127 // try to verify with any associated pubkeys
128 for _, pk := range pubKeys {
129 if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok {
130 verified = true
131 break
132 }
133 }
134
135 hashToVerified[c.This] = verified
136 }
137 }
138
139 return hashToVerified, nil
140}
141
142func randomString(n int) string {
143 const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
144 result := make([]byte, n)
145
146 for i := 0; i < n; i++ {
147 n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
148 result[i] = letters[n.Int64()]
149 }
150
151 return string(result)
152}