This repository has no description
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 rp.pages.RepoLog(w, pages.RepoLogParams{
177 BaseParams: pages.BaseParamsFromContext(r.Context()),
178 TagMap: tagMap,
179 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
180 RepoLogResponse: xrpcResp,
181 EmailToDid: emailToDidMap,
182 VerifiedCommits: vc,
183 })
184}
185
186func (rp *Repo) Commit(w http.ResponseWriter, r *http.Request) {
187 l := rp.logger.With("handler", "RepoCommit")
188
189 f, err := rp.repoResolver.Resolve(r)
190 if err != nil {
191 l.Error("failed to fully resolve repo", "err", err)
192 return
193 }
194 ref := chi.URLParam(r, "ref")
195 ref, _ = url.PathUnescape(ref)
196
197 var diffOpts types.DiffOpts
198 if d := r.URL.Query().Get("diff"); d == "split" {
199 diffOpts.Split = true
200 }
201
202 if !plumbing.IsHash(ref) {
203 rp.pages.Error404(w)
204 return
205 }
206
207 scheme := "http"
208 if !rp.config.Core.Dev {
209 scheme = "https"
210 }
211 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
212 xrpcc := &indigoxrpc.Client{
213 Host: host,
214 }
215
216 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, f.RepoIdentifier())
217 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
218 l.Error("failed to call XRPC repo.diff", "xrpcerr", xrpcerr, "err", err)
219 rp.pages.Error503(w)
220 return
221 }
222
223 var result types.RepoCommitResponse
224 if err := json.Unmarshal(xrpcBytes, &result); err != nil {
225 l.Error("failed to decode XRPC response", "err", err)
226 rp.pages.Error503(w)
227 return
228 }
229
230 emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Committer.Email, result.Diff.Commit.Author.Email}, true)
231 if err != nil {
232 l.Error("failed to get email to did mapping", "err", err)
233 }
234
235 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit})
236 if err != nil {
237 l.Error("failed to GetVerifiedCommits", "err", err)
238 }
239
240 user := rp.oauth.GetMultiAccountUser(r)
241 pipelines, err := getPipelineStatuses(r.Context(), f, []string{result.Diff.Commit.This})
242 if err != nil {
243 l.Error("failed to getPipelineStatuses", "err", err)
244 // non-fatal
245 }
246 var pipeline *types.Pipeline
247 if p, ok := pipelines[result.Diff.Commit.This]; ok {
248 pipeline = &p
249 }
250
251 rp.pages.RepoCommit(w, pages.RepoCommitParams{
252 BaseParams: pages.BaseParamsFromContext(r.Context()),
253 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
254 RepoCommitResponse: result,
255 EmailToDid: emailToDidMap,
256 VerifiedCommit: vc,
257 Pipeline: pipeline,
258 DiffOpts: diffOpts,
259 })
260}