forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at knot-xrpc 8.2 kB view raw
1package repo 2 3import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "net/http" 8 "slices" 9 "sort" 10 "strings" 11 12 "tangled.sh/tangled.sh/core/api/tangled" 13 "tangled.sh/tangled.sh/core/appview/commitverify" 14 "tangled.sh/tangled.sh/core/appview/db" 15 "tangled.sh/tangled.sh/core/appview/oauth" 16 "tangled.sh/tangled.sh/core/appview/pages" 17 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 18 "tangled.sh/tangled.sh/core/appview/reporesolver" 19 "tangled.sh/tangled.sh/core/knotclient" 20 "tangled.sh/tangled.sh/core/types" 21 22 "github.com/go-chi/chi/v5" 23 "github.com/go-enry/go-enry/v2" 24) 25 26func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) { 27 ref := chi.URLParam(r, "ref") 28 f, err := rp.repoResolver.Resolve(r) 29 if err != nil { 30 log.Println("failed to fully resolve repo", err) 31 return 32 } 33 34 us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 35 if err != nil { 36 log.Printf("failed to create unsigned client for %s", f.Knot) 37 rp.pages.Error503(w) 38 return 39 } 40 41 result, err := us.Index(f.OwnerDid(), f.RepoName, ref) 42 if err != nil { 43 rp.pages.Error503(w) 44 log.Println("failed to reach knotserver", err) 45 return 46 } 47 48 tagMap := make(map[string][]string) 49 for _, tag := range result.Tags { 50 hash := tag.Hash 51 if tag.Tag != nil { 52 hash = tag.Tag.Target.String() 53 } 54 tagMap[hash] = append(tagMap[hash], tag.Name) 55 } 56 57 for _, branch := range result.Branches { 58 hash := branch.Hash 59 tagMap[hash] = append(tagMap[hash], branch.Name) 60 } 61 62 sortFiles(result.Files) 63 64 slices.SortFunc(result.Branches, func(a, b types.Branch) int { 65 if a.Name == result.Ref { 66 return -1 67 } 68 if a.IsDefault { 69 return -1 70 } 71 if b.IsDefault { 72 return 1 73 } 74 if a.Commit != nil && b.Commit != nil { 75 if a.Commit.Committer.When.Before(b.Commit.Committer.When) { 76 return 1 77 } else { 78 return -1 79 } 80 } 81 return strings.Compare(a.Name, b.Name) * -1 82 }) 83 84 commitCount := len(result.Commits) 85 branchCount := len(result.Branches) 86 tagCount := len(result.Tags) 87 fileCount := len(result.Files) 88 89 commitCount, branchCount, tagCount = balanceIndexItems(commitCount, branchCount, tagCount, fileCount) 90 commitsTrunc := result.Commits[:min(commitCount, len(result.Commits))] 91 tagsTrunc := result.Tags[:min(tagCount, len(result.Tags))] 92 branchesTrunc := result.Branches[:min(branchCount, len(result.Branches))] 93 94 emails := uniqueEmails(commitsTrunc) 95 emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true) 96 if err != nil { 97 log.Println("failed to get email to did map", err) 98 } 99 100 vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, commitsTrunc) 101 if err != nil { 102 log.Println(err) 103 } 104 105 user := rp.oauth.GetUser(r) 106 repoInfo := f.RepoInfo(user) 107 108 secret, err := db.GetRegistrationKey(rp.db, f.Knot) 109 if err != nil { 110 log.Printf("failed to get registration key for %s: %s", f.Knot, err) 111 rp.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 112 } 113 114 signedClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) 115 if err != nil { 116 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 117 return 118 } 119 120 var forkInfo *types.ForkInfo 121 if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) { 122 forkInfo, err = getForkInfo(r, repoInfo, rp, f, user, signedClient) 123 if err != nil { 124 log.Printf("Failed to fetch fork information: %v", err) 125 return 126 } 127 } 128 129 // TODO: a bit dirty 130 languageInfo, err := rp.getLanguageInfo(f, signedClient, chi.URLParam(r, "ref") == "") 131 if err != nil { 132 log.Printf("failed to compute language percentages: %s", err) 133 // non-fatal 134 } 135 136 var shas []string 137 for _, c := range commitsTrunc { 138 shas = append(shas, c.Hash.String()) 139 } 140 pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas) 141 if err != nil { 142 log.Printf("failed to fetch pipeline statuses: %s", err) 143 // non-fatal 144 } 145 146 rp.pages.RepoIndexPage(w, pages.RepoIndexParams{ 147 LoggedInUser: user, 148 RepoInfo: repoInfo, 149 TagMap: tagMap, 150 RepoIndexResponse: *result, 151 CommitsTrunc: commitsTrunc, 152 TagsTrunc: tagsTrunc, 153 ForkInfo: forkInfo, 154 BranchesTrunc: branchesTrunc, 155 EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 156 VerifiedCommits: vc, 157 Languages: languageInfo, 158 Pipelines: pipelines, 159 }) 160} 161 162func (rp *Repo) getLanguageInfo( 163 f *reporesolver.ResolvedRepo, 164 signedClient *knotclient.SignedClient, 165 isDefaultRef bool, 166) ([]types.RepoLanguageDetails, error) { 167 // first attempt to fetch from db 168 langs, err := db.GetRepoLanguages( 169 rp.db, 170 db.FilterEq("repo_at", f.RepoAt), 171 db.FilterEq("ref", f.Ref), 172 ) 173 174 if err != nil || langs == nil { 175 // non-fatal, fetch langs from ks 176 ls, err := signedClient.RepoLanguages(f.OwnerDid(), f.RepoName, f.Ref) 177 if err != nil { 178 return nil, err 179 } 180 if ls == nil { 181 return nil, nil 182 } 183 184 for l, s := range ls.Languages { 185 langs = append(langs, db.RepoLanguage{ 186 RepoAt: f.RepoAt, 187 Ref: f.Ref, 188 IsDefaultRef: isDefaultRef, 189 Language: l, 190 Bytes: s, 191 }) 192 } 193 194 // update appview's cache 195 err = db.InsertRepoLanguages(rp.db, langs) 196 if err != nil { 197 // non-fatal 198 log.Println("failed to cache lang results", err) 199 } 200 } 201 202 var total int64 203 for _, l := range langs { 204 total += l.Bytes 205 } 206 207 var languageStats []types.RepoLanguageDetails 208 for _, l := range langs { 209 percentage := float32(l.Bytes) / float32(total) * 100 210 color := enry.GetColor(l.Language) 211 languageStats = append(languageStats, types.RepoLanguageDetails{ 212 Name: l.Language, 213 Percentage: percentage, 214 Color: color, 215 }) 216 } 217 218 sort.Slice(languageStats, func(i, j int) bool { 219 if languageStats[i].Name == enry.OtherLanguage { 220 return false 221 } 222 if languageStats[j].Name == enry.OtherLanguage { 223 return true 224 } 225 if languageStats[i].Percentage != languageStats[j].Percentage { 226 return languageStats[i].Percentage > languageStats[j].Percentage 227 } 228 return languageStats[i].Name < languageStats[j].Name 229 }) 230 231 return languageStats, nil 232} 233 234func getForkInfo( 235 r *http.Request, 236 repoInfo repoinfo.RepoInfo, 237 rp *Repo, 238 f *reporesolver.ResolvedRepo, 239 user *oauth.User, 240 signedClient *knotclient.SignedClient, 241) (*types.ForkInfo, error) { 242 if user == nil { 243 return nil, nil 244 } 245 246 forkInfo := types.ForkInfo{ 247 IsFork: repoInfo.Source != nil, 248 Status: types.UpToDate, 249 } 250 251 if !forkInfo.IsFork { 252 forkInfo.IsFork = false 253 return &forkInfo, nil 254 } 255 256 us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, rp.config.Core.Dev) 257 if err != nil { 258 log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot) 259 return nil, err 260 } 261 262 result, err := us.Branches(repoInfo.Source.Did, repoInfo.Source.Name) 263 if err != nil { 264 log.Println("failed to reach knotserver", err) 265 return nil, err 266 } 267 268 if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool { 269 return branch.Name == f.Ref 270 }) { 271 forkInfo.Status = types.MissingBranch 272 return &forkInfo, nil 273 } 274 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 return nil, err 284 } 285 286 resp, err := tangled.RepoHiddenRef( 287 r.Context(), 288 client, 289 &tangled.RepoHiddenRef_Input{ 290 ForkRef: f.Ref, 291 RemoteRef: f.Ref, 292 Repo: string(f.RepoAt), 293 }, 294 ) 295 if err != nil || !resp.Success { 296 if err != nil { 297 log.Printf("failed to update tracking branch: %s", err) 298 } else { 299 log.Printf("failed to update tracking branch: success=false") 300 } 301 return nil, fmt.Errorf("failed to update tracking branch") 302 } 303 304 hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref) 305 306 var status types.AncestorCheckResponse 307 forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef) 308 if err != nil { 309 log.Printf("failed to check if fork is ahead/behind: %s", err) 310 return nil, err 311 } 312 313 if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil { 314 log.Printf("failed to decode fork status: %s", err) 315 return nil, err 316 } 317 318 forkInfo.Status = status.Status 319 return &forkInfo, nil 320}