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