this repo has no description
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 // secret, err := db.GetRegistrationKey(rp.db, f.Knot)
105 // if err != nil {
106 // log.Printf("failed to get registration key for %s: %s", f.Knot, err)
107 // rp.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
108 // }
109
110 // signedClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
111 // if err != nil {
112 // log.Printf("failed to create signed client for %s: %s", f.Knot, err)
113 // return
114 // }
115
116 // var forkInfo *types.ForkInfo
117 // if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
118 // forkInfo, err = getForkInfo(r, repoInfo, rp, f, result.Ref, user, signedClient)
119 // if err != nil {
120 // log.Printf("Failed to fetch fork information: %v", err)
121 // return
122 // }
123 // }
124
125 // TODO: a bit dirty
126 languageInfo, err := rp.getLanguageInfo(f, us, result.Ref, ref == "")
127 if err != nil {
128 log.Printf("failed to compute language percentages: %s", err)
129 // non-fatal
130 }
131
132 var shas []string
133 for _, c := range commitsTrunc {
134 shas = append(shas, c.Hash.String())
135 }
136 pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
137 if err != nil {
138 log.Printf("failed to fetch pipeline statuses: %s", err)
139 // non-fatal
140 }
141
142 rp.pages.RepoIndexPage(w, pages.RepoIndexParams{
143 LoggedInUser: user,
144 RepoInfo: repoInfo,
145 TagMap: tagMap,
146 RepoIndexResponse: *result,
147 CommitsTrunc: commitsTrunc,
148 TagsTrunc: tagsTrunc,
149 // ForkInfo: forkInfo, // TODO: reinstate this after xrpc properly lands
150 BranchesTrunc: branchesTrunc,
151 EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap),
152 VerifiedCommits: vc,
153 Languages: languageInfo,
154 Pipelines: pipelines,
155 })
156}
157
158func (rp *Repo) getLanguageInfo(
159 f *reporesolver.ResolvedRepo,
160 us *knotclient.UnsignedClient,
161 currentRef string,
162 isDefaultRef bool,
163) ([]types.RepoLanguageDetails, error) {
164 // first attempt to fetch from db
165 langs, err := db.GetRepoLanguages(
166 rp.db,
167 db.FilterEq("repo_at", f.RepoAt()),
168 db.FilterEq("ref", currentRef),
169 )
170
171 if err != nil || langs == nil {
172 // non-fatal, fetch langs from ks
173 ls, err := us.RepoLanguages(f.OwnerDid(), f.Name, currentRef)
174 if err != nil {
175 return nil, err
176 }
177 if ls == nil {
178 return nil, nil
179 }
180
181 for l, s := range ls.Languages {
182 langs = append(langs, db.RepoLanguage{
183 RepoAt: f.RepoAt(),
184 Ref: currentRef,
185 IsDefaultRef: isDefaultRef,
186 Language: l,
187 Bytes: s,
188 })
189 }
190
191 // update appview's cache
192 err = db.InsertRepoLanguages(rp.db, langs)
193 if err != nil {
194 // non-fatal
195 log.Println("failed to cache lang results", err)
196 }
197 }
198
199 var total int64
200 for _, l := range langs {
201 total += l.Bytes
202 }
203
204 var languageStats []types.RepoLanguageDetails
205 for _, l := range langs {
206 percentage := float32(l.Bytes) / float32(total) * 100
207 color := enry.GetColor(l.Language)
208 languageStats = append(languageStats, types.RepoLanguageDetails{
209 Name: l.Language,
210 Percentage: percentage,
211 Color: color,
212 })
213 }
214
215 sort.Slice(languageStats, func(i, j int) bool {
216 if languageStats[i].Name == enry.OtherLanguage {
217 return false
218 }
219 if languageStats[j].Name == enry.OtherLanguage {
220 return true
221 }
222 if languageStats[i].Percentage != languageStats[j].Percentage {
223 return languageStats[i].Percentage > languageStats[j].Percentage
224 }
225 return languageStats[i].Name < languageStats[j].Name
226 })
227
228 return languageStats, nil
229}
230
231// func getForkInfo(
232// r *http.Request,
233// repoInfo repoinfo.RepoInfo,
234// rp *Repo,
235// f *reporesolver.ResolvedRepo,
236// currentRef string,
237// user *oauth.User,
238// signedClient *knotclient.SignedClient,
239// ) (*types.ForkInfo, error) {
240// if user == nil {
241// return nil, nil
242// }
243//
244// forkInfo := types.ForkInfo{
245// IsFork: repoInfo.Source != nil,
246// Status: types.UpToDate,
247// }
248//
249// if !forkInfo.IsFork {
250// forkInfo.IsFork = false
251// return &forkInfo, nil
252// }
253//
254// us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, rp.config.Core.Dev)
255// if err != nil {
256// log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot)
257// return nil, err
258// }
259//
260// result, err := us.Branches(repoInfo.Source.Did, repoInfo.Source.Name)
261// if err != nil {
262// log.Println("failed to reach knotserver", err)
263// return nil, err
264// }
265//
266// if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
267// return branch.Name == currentRef
268// }) {
269// forkInfo.Status = types.MissingBranch
270// return &forkInfo, nil
271// }
272//
273// <<<<<<< Conflict 1 of 2
274// %%%%%%% Changes from base #1 to side #1
275// client, err := rp.oauth.ServiceClient(
276// r,
277// oauth.WithService(f.Knot),
278// oauth.WithLxm(tangled.RepoHiddenRefNSID),
279// oauth.WithDev(rp.config.Core.Dev),
280// )
281// if err != nil {
282// log.Printf("failed to connect to knot server: %v", err)
283// %%%%%%% Changes from base #2 to side #2
284// - newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, currentRef, currentRef)
285// + newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, f.Ref, f.Ref)
286// if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent {
287// log.Printf("failed to update tracking branch: %s", err)
288// +++++++ Contents of side #3
289// client, err := rp.oauth.ServiceClient(
290// r,
291// oauth.WithService(f.Knot),
292// oauth.WithLxm(tangled.RepoHiddenRefNSID),
293// oauth.WithDev(rp.config.Core.Dev),
294// )
295// if err != nil {
296// log.Printf("failed to connect to knot server: %v", err)
297// >>>>>>> Conflict 1 of 2 ends
298// return nil, err
299// }
300//
301// <<<<<<< Conflict 2 of 2
302// %%%%%%% Changes from base #1 to side #1
303// resp, err := tangled.RepoHiddenRef(
304// r.Context(),
305// client,
306// &tangled.RepoHiddenRef_Input{
307// - ForkRef: f.Ref,
308// - RemoteRef: f.Ref,
309// + ForkRef: currentRef,
310// + RemoteRef: currentRef,
311// Repo: f.RepoAt().String(),
312// },
313// )
314// if err != nil || !resp.Success {
315// if err != nil {
316// log.Printf("failed to update tracking branch: %s", err)
317// } else {
318// log.Printf("failed to update tracking branch: success=false")
319// }
320// return nil, fmt.Errorf("failed to update tracking branch")
321// }
322//
323// - hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
324// + hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
325//
326// %%%%%%% Changes from base #2 to side #2
327// - hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
328// + hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
329//
330// +++++++ Contents of side #3
331// resp, err := tangled.RepoHiddenRef(
332// r.Context(),
333// client,
334// &tangled.RepoHiddenRef_Input{
335// ForkRef: currentRef,
336// RemoteRef: currentRef,
337// Repo: f.RepoAt().String(),
338// },
339// )
340// if err != nil || !resp.Success {
341// if err != nil {
342// log.Printf("failed to update tracking branch: %s", err)
343// } else {
344// log.Printf("failed to update tracking branch: success=false")
345// }
346// return nil, fmt.Errorf("failed to update tracking branch")
347// }
348//
349// hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
350// >>>>>>> Conflict 2 of 2 ends
351// var status types.AncestorCheckResponse
352// forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt()), repoInfo.Name, currentRef, hiddenRef)
353// if err != nil {
354// log.Printf("failed to check if fork is ahead/behind: %s", err)
355// return nil, err
356// }
357//
358// if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil {
359// log.Printf("failed to decode fork status: %s", err)
360// return nil, err
361// }
362//
363// forkInfo.Status = status.Status
364// return &forkInfo, nil
365// }