forked from tangled.org/core
this repo has no description
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/appview/commitverify" 13 "tangled.sh/tangled.sh/core/appview/db" 14 "tangled.sh/tangled.sh/core/appview/oauth" 15 "tangled.sh/tangled.sh/core/appview/pages" 16 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 17 "tangled.sh/tangled.sh/core/appview/reporesolver" 18 "tangled.sh/tangled.sh/core/knotclient" 19 "tangled.sh/tangled.sh/core/types" 20 21 "github.com/go-chi/chi/v5" 22 "github.com/go-enry/go-enry/v2" 23) 24 25func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) { 26 ref := chi.URLParam(r, "ref") 27 f, err := rp.repoResolver.Resolve(r) 28 if err != nil { 29 log.Println("failed to fully resolve repo", err) 30 return 31 } 32 33 us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 34 if err != nil { 35 log.Printf("failed to create unsigned client for %s", f.Knot) 36 rp.pages.Error503(w) 37 return 38 } 39 40 result, err := us.Index(f.OwnerDid(), f.RepoName, ref) 41 if err != nil { 42 rp.pages.Error503(w) 43 log.Println("failed to reach knotserver", err) 44 return 45 } 46 47 tagMap := make(map[string][]string) 48 for _, tag := range result.Tags { 49 hash := tag.Hash 50 if tag.Tag != nil { 51 hash = tag.Tag.Target.String() 52 } 53 tagMap[hash] = append(tagMap[hash], tag.Name) 54 } 55 56 for _, branch := range result.Branches { 57 hash := branch.Hash 58 tagMap[hash] = append(tagMap[hash], branch.Name) 59 } 60 61 slices.SortFunc(result.Branches, func(a, b types.Branch) int { 62 if a.Name == result.Ref { 63 return -1 64 } 65 if a.IsDefault { 66 return -1 67 } 68 if b.IsDefault { 69 return 1 70 } 71 if a.Commit != nil && b.Commit != nil { 72 if a.Commit.Committer.When.Before(b.Commit.Committer.When) { 73 return 1 74 } else { 75 return -1 76 } 77 } 78 return strings.Compare(a.Name, b.Name) * -1 79 }) 80 81 commitCount := len(result.Commits) 82 branchCount := len(result.Branches) 83 tagCount := len(result.Tags) 84 fileCount := len(result.Files) 85 86 commitCount, branchCount, tagCount = balanceIndexItems(commitCount, branchCount, tagCount, fileCount) 87 commitsTrunc := result.Commits[:min(commitCount, len(result.Commits))] 88 tagsTrunc := result.Tags[:min(tagCount, len(result.Tags))] 89 branchesTrunc := result.Branches[:min(branchCount, len(result.Branches))] 90 91 emails := uniqueEmails(commitsTrunc) 92 emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true) 93 if err != nil { 94 log.Println("failed to get email to did map", err) 95 } 96 97 vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, commitsTrunc) 98 if err != nil { 99 log.Println(err) 100 } 101 102 user := rp.oauth.GetUser(r) 103 repoInfo := f.RepoInfo(user) 104 105 secret, err := db.GetRegistrationKey(rp.db, f.Knot) 106 if err != nil { 107 log.Printf("failed to get registration key for %s: %s", f.Knot, err) 108 rp.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 109 } 110 111 signedClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) 112 if err != nil { 113 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 114 return 115 } 116 117 var forkInfo *types.ForkInfo 118 if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) { 119 forkInfo, err = getForkInfo(repoInfo, rp, f, user, signedClient) 120 if err != nil { 121 log.Printf("Failed to fetch fork information: %v", err) 122 return 123 } 124 } 125 126 languageInfo, err := getLanguageInfo(repoInfo, rp, f, user, signedClient, 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 := rp.getPipelineStatuses(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, 150 BranchesTrunc: branchesTrunc, 151 EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 152 VerifiedCommits: vc, 153 Languages: languageInfo, 154 Pipelines: pipelines, 155 }) 156 return 157} 158 159func getLanguageInfo( 160 repoInfo repoinfo.RepoInfo, 161 rp *Repo, 162 f *reporesolver.ResolvedRepo, 163 user *oauth.User, 164 signedClient *knotclient.SignedClient, 165 ref string, 166) ([]types.RepoLanguageDetails, error) { 167 repoLanguages, err := signedClient.RepoLanguages(f.OwnerDid(), f.RepoName, ref) 168 if err != nil { 169 return []types.RepoLanguageDetails{}, err 170 } 171 if repoLanguages == nil { 172 repoLanguages = &types.RepoLanguageResponse{Languages: make(map[string]int)} 173 } 174 175 totalLanguageFileCount := 0 176 for _, fileCount := range repoLanguages.Languages { 177 totalLanguageFileCount += fileCount 178 } 179 180 var languageStats []types.RepoLanguageDetails 181 var otherLanguageStat *types.RepoLanguageDetails 182 var otherPercentage float32 = 0 183 184 for fileType, fileCount := range repoLanguages.Languages { 185 percentage := (float32(fileCount) / float32(totalLanguageFileCount)) * 100 186 187 if percentage <= 0.1 { 188 otherPercentage += percentage 189 continue 190 } 191 192 // Exclude languages 193 if fileType == "Text" { 194 otherPercentage += percentage 195 continue 196 } 197 198 color := enry.GetColor(fileType) 199 200 if fileType == "Other" { 201 otherLanguageStat = &types.RepoLanguageDetails{Name: fileType, Percentage: percentage, Color: color} 202 } else { 203 languageStats = append(languageStats, types.RepoLanguageDetails{Name: fileType, Percentage: percentage, Color: color}) 204 } 205 } 206 207 sort.Slice(languageStats, func(i, j int) bool { 208 return languageStats[i].Percentage > languageStats[j].Percentage 209 }) 210 211 if otherLanguageStat != nil { 212 otherLanguageStat.Percentage += otherPercentage 213 languageStats = append(languageStats, *otherLanguageStat) 214 } 215 216 return languageStats, nil 217} 218 219func getForkInfo( 220 repoInfo repoinfo.RepoInfo, 221 rp *Repo, 222 f *reporesolver.ResolvedRepo, 223 user *oauth.User, 224 signedClient *knotclient.SignedClient, 225) (*types.ForkInfo, error) { 226 if user == nil { 227 return nil, nil 228 } 229 230 forkInfo := types.ForkInfo{ 231 IsFork: repoInfo.Source != nil, 232 Status: types.UpToDate, 233 } 234 235 if !forkInfo.IsFork { 236 forkInfo.IsFork = false 237 return &forkInfo, nil 238 } 239 240 us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, rp.config.Core.Dev) 241 if err != nil { 242 log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot) 243 return nil, err 244 } 245 246 result, err := us.Branches(repoInfo.Source.Did, repoInfo.Source.Name) 247 if err != nil { 248 log.Println("failed to reach knotserver", err) 249 return nil, err 250 } 251 252 if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool { 253 return branch.Name == f.Ref 254 }) { 255 forkInfo.Status = types.MissingBranch 256 return &forkInfo, nil 257 } 258 259 newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, f.Ref, f.Ref) 260 if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent { 261 log.Printf("failed to update tracking branch: %s", err) 262 return nil, err 263 } 264 265 hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref) 266 267 var status types.AncestorCheckResponse 268 forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef) 269 if err != nil { 270 log.Printf("failed to check if fork is ahead/behind: %s", err) 271 return nil, err 272 } 273 274 if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil { 275 log.Printf("failed to decode fork status: %s", err) 276 return nil, err 277 } 278 279 forkInfo.Status = status.Status 280 return &forkInfo, nil 281}