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}