Monorepo for Tangled tangled.org
1

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / repo / log.go
7.1 kB 271 lines
1package repo 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "strconv" 10 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/commitverify" 13 "tangled.org/core/appview/db" 14 "tangled.org/core/appview/pages" 15 "tangled.org/core/types" 16 xrpcclient "tangled.org/core/xrpc/xrpcclient" 17 18 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 19 "github.com/go-chi/chi/v5" 20 "github.com/go-git/go-git/v5/plumbing" 21) 22 23func (rp *Repo) CommitRawDiff(w http.ResponseWriter, r *http.Request) { 24 rp.serveRawCommit(w, r, "diff") 25} 26 27func (rp *Repo) CommitRawPatch(w http.ResponseWriter, r *http.Request) { 28 rp.serveRawCommit(w, r, "patch") 29} 30 31func (rp *Repo) serveRawCommit(w http.ResponseWriter, r *http.Request, format string) { 32 l := rp.logger.With("handler", "CommitRaw", "format", format) 33 34 f, err := rp.repoResolver.Resolve(r) 35 if err != nil { 36 l.Error("failed to resolve repo", "err", err) 37 return 38 } 39 40 ref := chi.URLParam(r, "ref") 41 ref, _ = url.PathUnescape(ref) 42 43 if !plumbing.IsHash(ref) { 44 rp.pages.Error404(w) 45 return 46 } 47 48 scheme := "http" 49 if !rp.config.Core.Dev { 50 scheme = "https" 51 } 52 53 xrpcc := &indigoxrpc.Client{ 54 Host: fmt.Sprintf("%s://%s", scheme, f.Knot), 55 } 56 57 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, f.RepoIdentifier()) 58 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 59 l.Error("failed to call XRPC repo.diff", "xrpcerr", xrpcerr, "err", err) 60 rp.pages.Error503(w) 61 return 62 } 63 64 var result types.RepoCommitResponse 65 if err := json.Unmarshal(xrpcBytes, &result); err != nil { 66 l.Error("failed to decode XRPC response", "err", err) 67 rp.pages.Error503(w) 68 return 69 } 70 71 filename := ref[:7] + "." + format 72 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 73 w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename)) 74 75 switch format { 76 case "patch": 77 io.WriteString(w, renderFormatPatch(result.Diff)) 78 default: 79 io.WriteString(w, renderUnifiedDiff(result.Diff)) 80 } 81} 82 83func (rp *Repo) Log(w http.ResponseWriter, r *http.Request) { 84 l := rp.logger.With("handler", "RepoLog") 85 86 f, err := rp.repoResolver.Resolve(r) 87 if err != nil { 88 l.Error("failed to fully resolve repo", "err", err) 89 return 90 } 91 92 page := 1 93 if r.URL.Query().Get("page") != "" { 94 page, err = strconv.Atoi(r.URL.Query().Get("page")) 95 if err != nil { 96 page = 1 97 } 98 } 99 100 ref := chi.URLParam(r, "ref") 101 ref, _ = url.PathUnescape(ref) 102 103 xrpcc := &indigoxrpc.Client{Host: rp.config.KnotMirror.Url} 104 105 limit := int64(60) 106 cursor := "" 107 if page > 1 { 108 // Convert page number to cursor (offset) 109 offset := (page - 1) * int(limit) 110 cursor = strconv.Itoa(offset) 111 } 112 113 xrpcBytes, err := tangled.GitTempListCommits(r.Context(), xrpcc, cursor, limit, ref, f.RepoDid) 114 if err != nil { 115 l.Error("failed to call XRPC repo.log", "err", err) 116 rp.pages.Error503(w) 117 return 118 } 119 120 var xrpcResp types.RepoLogResponse 121 if err := json.Unmarshal(xrpcBytes, &xrpcResp); err != nil { 122 l.Error("failed to decode XRPC response", "err", err) 123 rp.pages.Error503(w) 124 return 125 } 126 127 tagBytes, err := tangled.GitTempListTags(r.Context(), xrpcc, "", 0, f.RepoDid) 128 if err != nil { 129 l.Error("failed to call XRPC repo.tags", "err", err) 130 rp.pages.Error503(w) 131 return 132 } 133 134 tagMap := make(map[string][]string) 135 if tagBytes != nil { 136 var tagResp types.RepoTagsResponse 137 if err := json.Unmarshal(tagBytes, &tagResp); err == nil { 138 for _, tag := range tagResp.Tags { 139 hash := tag.Hash 140 if tag.Tag != nil { 141 hash = tag.Tag.Target.String() 142 } 143 tagMap[hash] = append(tagMap[hash], tag.Name) 144 } 145 } 146 } 147 148 branchBytes, err := tangled.GitTempListBranches(r.Context(), xrpcc, "", 0, f.RepoDid) 149 if err != nil { 150 l.Error("failed to call XRPC repo.branches", "err", err) 151 rp.pages.Error503(w) 152 return 153 } 154 155 if branchBytes != nil { 156 var branchResp types.RepoBranchesResponse 157 if err := json.Unmarshal(branchBytes, &branchResp); err == nil { 158 for _, branch := range branchResp.Branches { 159 tagMap[branch.Hash] = append(tagMap[branch.Hash], branch.Name) 160 } 161 } 162 } 163 164 user := rp.oauth.GetMultiAccountUser(r) 165 166 emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(xrpcResp.Commits), true) 167 if err != nil { 168 l.Error("failed to fetch email to did mapping", "err", err) 169 } 170 171 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, xrpcResp.Commits) 172 if err != nil { 173 l.Error("failed to GetVerifiedObjectCommits", "err", err) 174 } 175 176 var shas []string 177 for _, c := range xrpcResp.Commits { 178 shas = append(shas, c.Hash.String()) 179 } 180 pipelines, err := getPipelineStatuses(r.Context(), f, shas) 181 if err != nil { 182 l.Error("failed to getPipelineStatuses", "err", err) 183 // non-fatal 184 } 185 186 rp.pages.RepoLog(w, pages.RepoLogParams{ 187 BaseParams: pages.BaseParamsFromContext(r.Context()), 188 TagMap: tagMap, 189 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 190 RepoLogResponse: xrpcResp, 191 EmailToDid: emailToDidMap, 192 VerifiedCommits: vc, 193 Pipelines: pipelines, 194 }) 195} 196 197func (rp *Repo) Commit(w http.ResponseWriter, r *http.Request) { 198 l := rp.logger.With("handler", "RepoCommit") 199 200 f, err := rp.repoResolver.Resolve(r) 201 if err != nil { 202 l.Error("failed to fully resolve repo", "err", err) 203 return 204 } 205 ref := chi.URLParam(r, "ref") 206 ref, _ = url.PathUnescape(ref) 207 208 var diffOpts types.DiffOpts 209 if d := r.URL.Query().Get("diff"); d == "split" { 210 diffOpts.Split = true 211 } 212 213 if !plumbing.IsHash(ref) { 214 rp.pages.Error404(w) 215 return 216 } 217 218 scheme := "http" 219 if !rp.config.Core.Dev { 220 scheme = "https" 221 } 222 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 223 xrpcc := &indigoxrpc.Client{ 224 Host: host, 225 } 226 227 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, f.RepoIdentifier()) 228 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 229 l.Error("failed to call XRPC repo.diff", "xrpcerr", xrpcerr, "err", err) 230 rp.pages.Error503(w) 231 return 232 } 233 234 var result types.RepoCommitResponse 235 if err := json.Unmarshal(xrpcBytes, &result); err != nil { 236 l.Error("failed to decode XRPC response", "err", err) 237 rp.pages.Error503(w) 238 return 239 } 240 241 emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Committer.Email, result.Diff.Commit.Author.Email}, true) 242 if err != nil { 243 l.Error("failed to get email to did mapping", "err", err) 244 } 245 246 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit}) 247 if err != nil { 248 l.Error("failed to GetVerifiedCommits", "err", err) 249 } 250 251 user := rp.oauth.GetMultiAccountUser(r) 252 pipelines, err := getPipelineStatuses(r.Context(), f, []string{result.Diff.Commit.This}) 253 if err != nil { 254 l.Error("failed to getPipelineStatuses", "err", err) 255 // non-fatal 256 } 257 var pipeline *types.Pipeline 258 if p, ok := pipelines[result.Diff.Commit.This]; ok { 259 pipeline = &p 260 } 261 262 rp.pages.RepoCommit(w, pages.RepoCommitParams{ 263 BaseParams: pages.BaseParamsFromContext(r.Context()), 264 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 265 RepoCommitResponse: result, 266 EmailToDid: emailToDidMap, 267 VerifiedCommit: vc, 268 Pipeline: pipeline, 269 DiffOpts: diffOpts, 270 }) 271}