forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package repo
2
3import (
4 "log"
5 "net/http"
6 "slices"
7 "sort"
8 "strings"
9
10 "tangled.sh/tangled.sh/core/appview/commitverify"
11 "tangled.sh/tangled.sh/core/appview/db"
12 "tangled.sh/tangled.sh/core/appview/pages"
13 "tangled.sh/tangled.sh/core/appview/reporesolver"
14 "tangled.sh/tangled.sh/core/knotclient"
15 "tangled.sh/tangled.sh/core/types"
16
17 "github.com/go-chi/chi/v5"
18 "github.com/go-enry/go-enry/v2"
19)
20
21func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
22 ref := chi.URLParam(r, "ref")
23
24 f, err := rp.repoResolver.Resolve(r)
25 if err != nil {
26 log.Println("failed to fully resolve repo", err)
27 return
28 }
29
30 us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
31 if err != nil {
32 log.Printf("failed to create unsigned client for %s", f.Knot)
33 rp.pages.Error503(w)
34 return
35 }
36
37 result, err := us.Index(f.OwnerDid(), f.Name, ref)
38 if err != nil {
39 rp.pages.Error503(w)
40 log.Println("failed to reach knotserver", err)
41 return
42 }
43
44 tagMap := make(map[string][]string)
45 for _, tag := range result.Tags {
46 hash := tag.Hash
47 if tag.Tag != nil {
48 hash = tag.Tag.Target.String()
49 }
50 tagMap[hash] = append(tagMap[hash], tag.Name)
51 }
52
53 for _, branch := range result.Branches {
54 hash := branch.Hash
55 tagMap[hash] = append(tagMap[hash], branch.Name)
56 }
57
58 sortFiles(result.Files)
59
60 slices.SortFunc(result.Branches, func(a, b types.Branch) int {
61 if a.Name == result.Ref {
62 return -1
63 }
64 if a.IsDefault {
65 return -1
66 }
67 if b.IsDefault {
68 return 1
69 }
70 if a.Commit != nil && b.Commit != nil {
71 if a.Commit.Committer.When.Before(b.Commit.Committer.When) {
72 return 1
73 } else {
74 return -1
75 }
76 }
77 return strings.Compare(a.Name, b.Name) * -1
78 })
79
80 commitCount := len(result.Commits)
81 branchCount := len(result.Branches)
82 tagCount := len(result.Tags)
83 fileCount := len(result.Files)
84
85 commitCount, branchCount, tagCount = balanceIndexItems(commitCount, branchCount, tagCount, fileCount)
86 commitsTrunc := result.Commits[:min(commitCount, len(result.Commits))]
87 tagsTrunc := result.Tags[:min(tagCount, len(result.Tags))]
88 branchesTrunc := result.Branches[:min(branchCount, len(result.Branches))]
89
90 emails := uniqueEmails(commitsTrunc)
91 emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true)
92 if err != nil {
93 log.Println("failed to get email to did map", err)
94 }
95
96 vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, commitsTrunc)
97 if err != nil {
98 log.Println(err)
99 }
100
101 user := rp.oauth.GetUser(r)
102 repoInfo := f.RepoInfo(user)
103
104 // TODO: a bit dirty
105 languageInfo, err := rp.getLanguageInfo(f, us, result.Ref, ref == "")
106 if err != nil {
107 log.Printf("failed to compute language percentages: %s", err)
108 // non-fatal
109 }
110
111 var shas []string
112 for _, c := range commitsTrunc {
113 shas = append(shas, c.Hash.String())
114 }
115 pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
116 if err != nil {
117 log.Printf("failed to fetch pipeline statuses: %s", err)
118 // non-fatal
119 }
120
121 rp.pages.RepoIndexPage(w, pages.RepoIndexParams{
122 LoggedInUser: user,
123 RepoInfo: repoInfo,
124 TagMap: tagMap,
125 RepoIndexResponse: *result,
126 CommitsTrunc: commitsTrunc,
127 TagsTrunc: tagsTrunc,
128 // ForkInfo: forkInfo, // TODO: reinstate this after xrpc properly lands
129 BranchesTrunc: branchesTrunc,
130 EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap),
131 VerifiedCommits: vc,
132 Languages: languageInfo,
133 Pipelines: pipelines,
134 })
135}
136
137func (rp *Repo) getLanguageInfo(
138 f *reporesolver.ResolvedRepo,
139 us *knotclient.UnsignedClient,
140 currentRef string,
141 isDefaultRef bool,
142) ([]types.RepoLanguageDetails, error) {
143 // first attempt to fetch from db
144 langs, err := db.GetRepoLanguages(
145 rp.db,
146 db.FilterEq("repo_at", f.RepoAt()),
147 db.FilterEq("ref", currentRef),
148 )
149
150 if err != nil || langs == nil {
151 // non-fatal, fetch langs from ks
152 ls, err := us.RepoLanguages(f.OwnerDid(), f.Name, currentRef)
153 if err != nil {
154 return nil, err
155 }
156 if ls == nil {
157 return nil, nil
158 }
159
160 for l, s := range ls.Languages {
161 langs = append(langs, db.RepoLanguage{
162 RepoAt: f.RepoAt(),
163 Ref: currentRef,
164 IsDefaultRef: isDefaultRef,
165 Language: l,
166 Bytes: s,
167 })
168 }
169
170 // update appview's cache
171 err = db.InsertRepoLanguages(rp.db, langs)
172 if err != nil {
173 // non-fatal
174 log.Println("failed to cache lang results", err)
175 }
176 }
177
178 var total int64
179 for _, l := range langs {
180 total += l.Bytes
181 }
182
183 var languageStats []types.RepoLanguageDetails
184 for _, l := range langs {
185 percentage := float32(l.Bytes) / float32(total) * 100
186 color := enry.GetColor(l.Language)
187 languageStats = append(languageStats, types.RepoLanguageDetails{
188 Name: l.Language,
189 Percentage: percentage,
190 Color: color,
191 })
192 }
193
194 sort.Slice(languageStats, func(i, j int) bool {
195 if languageStats[i].Name == enry.OtherLanguage {
196 return false
197 }
198 if languageStats[j].Name == enry.OtherLanguage {
199 return true
200 }
201 if languageStats[i].Percentage != languageStats[j].Percentage {
202 return languageStats[i].Percentage > languageStats[j].Percentage
203 }
204 return languageStats[i].Name < languageStats[j].Name
205 })
206
207 return languageStats, nil
208}