···
"tangled.org/core/api/tangled"
20
-
"tangled.org/core/appview/commitverify"
"tangled.org/core/appview/config"
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
27
-
"tangled.org/core/appview/pages/markup"
"tangled.org/core/appview/reporesolver"
"tangled.org/core/appview/validator"
xrpcclient "tangled.org/core/appview/xrpcclient"
"tangled.org/core/eventconsumer"
"tangled.org/core/idresolver"
33
-
"tangled.org/core/patchutil"
36
-
"tangled.org/core/types"
"tangled.org/core/xrpc/serviceauth"
comatproto "github.com/bluesky-social/indigo/api/atproto"
atpclient "github.com/bluesky-social/indigo/atproto/client"
"github.com/bluesky-social/indigo/atproto/syntax"
lexutil "github.com/bluesky-social/indigo/lex/util"
43
-
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-chi/chi/v5"
46
-
"github.com/go-git/go-git/v5/plumbing"
···
91
-
func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
92
-
l := rp.logger.With("handler", "DownloadArchive")
94
-
ref := chi.URLParam(r, "ref")
95
-
ref, _ = url.PathUnescape(ref)
97
-
f, err := rp.repoResolver.Resolve(r)
99
-
l.Error("failed to get repo and knot", "err", err)
104
-
if !rp.config.Core.Dev {
107
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
108
-
xrpcc := &indigoxrpc.Client{
112
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
113
-
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo)
114
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
115
-
l.Error("failed to call XRPC repo.archive", "err", xrpcerr)
116
-
rp.pages.Error503(w)
120
-
// Set headers for file download, just pass along whatever the knot specifies
121
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
122
-
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename)
123
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
124
-
w.Header().Set("Content-Type", "application/gzip")
125
-
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
127
-
// Write the archive data directly
128
-
w.Write(archiveBytes)
131
-
func (rp *Repo) RepoLog(w http.ResponseWriter, r *http.Request) {
132
-
l := rp.logger.With("handler", "RepoLog")
134
-
f, err := rp.repoResolver.Resolve(r)
136
-
l.Error("failed to fully resolve repo", "err", err)
141
-
if r.URL.Query().Get("page") != "" {
142
-
page, err = strconv.Atoi(r.URL.Query().Get("page"))
148
-
ref := chi.URLParam(r, "ref")
149
-
ref, _ = url.PathUnescape(ref)
152
-
if !rp.config.Core.Dev {
155
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
156
-
xrpcc := &indigoxrpc.Client{
163
-
// Convert page number to cursor (offset)
164
-
offset := (page - 1) * int(limit)
165
-
cursor = strconv.Itoa(offset)
168
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
169
-
xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo)
170
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
171
-
l.Error("failed to call XRPC repo.log", "err", xrpcerr)
172
-
rp.pages.Error503(w)
176
-
var xrpcResp types.RepoLogResponse
177
-
if err := json.Unmarshal(xrpcBytes, &xrpcResp); err != nil {
178
-
l.Error("failed to decode XRPC response", "err", err)
179
-
rp.pages.Error503(w)
183
-
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
184
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
185
-
l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
186
-
rp.pages.Error503(w)
190
-
tagMap := make(map[string][]string)
191
-
if tagBytes != nil {
192
-
var tagResp types.RepoTagsResponse
193
-
if err := json.Unmarshal(tagBytes, &tagResp); err == nil {
194
-
for _, tag := range tagResp.Tags {
196
-
if tag.Tag != nil {
197
-
hash = tag.Tag.Target.String()
199
-
tagMap[hash] = append(tagMap[hash], tag.Name)
204
-
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
205
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
206
-
l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
207
-
rp.pages.Error503(w)
211
-
if branchBytes != nil {
212
-
var branchResp types.RepoBranchesResponse
213
-
if err := json.Unmarshal(branchBytes, &branchResp); err == nil {
214
-
for _, branch := range branchResp.Branches {
215
-
tagMap[branch.Hash] = append(tagMap[branch.Hash], branch.Name)
220
-
user := rp.oauth.GetUser(r)
222
-
emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(xrpcResp.Commits), true)
224
-
l.Error("failed to fetch email to did mapping", "err", err)
227
-
vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits)
229
-
l.Error("failed to GetVerifiedObjectCommits", "err", err)
232
-
repoInfo := f.RepoInfo(user)
235
-
for _, c := range xrpcResp.Commits {
236
-
shas = append(shas, c.Hash.String())
238
-
pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
240
-
l.Error("failed to getPipelineStatuses", "err", err)
244
-
rp.pages.RepoLog(w, pages.RepoLogParams{
245
-
LoggedInUser: user,
247
-
RepoInfo: repoInfo,
248
-
RepoLogResponse: xrpcResp,
249
-
EmailToDid: emailToDidMap,
250
-
VerifiedCommits: vc,
251
-
Pipelines: pipelines,
255
-
func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {
256
-
l := rp.logger.With("handler", "RepoCommit")
258
-
f, err := rp.repoResolver.Resolve(r)
260
-
l.Error("failed to fully resolve repo", "err", err)
263
-
ref := chi.URLParam(r, "ref")
264
-
ref, _ = url.PathUnescape(ref)
266
-
var diffOpts types.DiffOpts
267
-
if d := r.URL.Query().Get("diff"); d == "split" {
268
-
diffOpts.Split = true
271
-
if !plumbing.IsHash(ref) {
272
-
rp.pages.Error404(w)
277
-
if !rp.config.Core.Dev {
280
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
281
-
xrpcc := &indigoxrpc.Client{
285
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
286
-
xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo)
287
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
288
-
l.Error("failed to call XRPC repo.diff", "err", xrpcerr)
289
-
rp.pages.Error503(w)
293
-
var result types.RepoCommitResponse
294
-
if err := json.Unmarshal(xrpcBytes, &result); err != nil {
295
-
l.Error("failed to decode XRPC response", "err", err)
296
-
rp.pages.Error503(w)
300
-
emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Committer.Email, result.Diff.Commit.Author.Email}, true)
302
-
l.Error("failed to get email to did mapping", "err", err)
305
-
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.NiceDiff{*result.Diff})
307
-
l.Error("failed to GetVerifiedCommits", "err", err)
310
-
user := rp.oauth.GetUser(r)
311
-
repoInfo := f.RepoInfo(user)
312
-
pipelines, err := getPipelineStatuses(rp.db, repoInfo, []string{result.Diff.Commit.This})
314
-
l.Error("failed to getPipelineStatuses", "err", err)
317
-
var pipeline *models.Pipeline
318
-
if p, ok := pipelines[result.Diff.Commit.This]; ok {
322
-
rp.pages.RepoCommit(w, pages.RepoCommitParams{
323
-
LoggedInUser: user,
324
-
RepoInfo: f.RepoInfo(user),
325
-
RepoCommitResponse: result,
326
-
EmailToDid: emailToDidMap,
327
-
VerifiedCommit: vc,
328
-
Pipeline: pipeline,
329
-
DiffOpts: diffOpts,
333
-
func (rp *Repo) RepoTree(w http.ResponseWriter, r *http.Request) {
334
-
l := rp.logger.With("handler", "RepoTree")
336
-
f, err := rp.repoResolver.Resolve(r)
338
-
l.Error("failed to fully resolve repo", "err", err)
342
-
ref := chi.URLParam(r, "ref")
343
-
ref, _ = url.PathUnescape(ref)
345
-
// if the tree path has a trailing slash, let's strip it
347
-
treePath := chi.URLParam(r, "*")
348
-
treePath, _ = url.PathUnescape(treePath)
349
-
treePath = strings.TrimSuffix(treePath, "/")
352
-
if !rp.config.Core.Dev {
355
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
356
-
xrpcc := &indigoxrpc.Client{
360
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
361
-
xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo)
362
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
363
-
l.Error("failed to call XRPC repo.tree", "err", xrpcerr)
364
-
rp.pages.Error503(w)
368
-
// Convert XRPC response to internal types.RepoTreeResponse
369
-
files := make([]types.NiceTree, len(xrpcResp.Files))
370
-
for i, xrpcFile := range xrpcResp.Files {
371
-
file := types.NiceTree{
372
-
Name: xrpcFile.Name,
373
-
Mode: xrpcFile.Mode,
374
-
Size: int64(xrpcFile.Size),
375
-
IsFile: xrpcFile.Is_file,
376
-
IsSubtree: xrpcFile.Is_subtree,
379
-
// Convert last commit info if present
380
-
if xrpcFile.Last_commit != nil {
381
-
commitWhen, _ := time.Parse(time.RFC3339, xrpcFile.Last_commit.When)
382
-
file.LastCommit = &types.LastCommitInfo{
383
-
Hash: plumbing.NewHash(xrpcFile.Last_commit.Hash),
384
-
Message: xrpcFile.Last_commit.Message,
392
-
result := types.RepoTreeResponse{
397
-
if xrpcResp.Parent != nil {
398
-
result.Parent = *xrpcResp.Parent
400
-
if xrpcResp.Dotdot != nil {
401
-
result.DotDot = *xrpcResp.Dotdot
403
-
if xrpcResp.Readme != nil {
404
-
result.ReadmeFileName = xrpcResp.Readme.Filename
405
-
result.Readme = xrpcResp.Readme.Contents
408
-
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
409
-
// so we can safely redirect to the "parent" (which is the same file).
410
-
if len(result.Files) == 0 && result.Parent == treePath {
411
-
redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent)
412
-
http.Redirect(w, r, redirectTo, http.StatusFound)
416
-
user := rp.oauth.GetUser(r)
418
-
var breadcrumbs [][]string
419
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
420
-
if treePath != "" {
421
-
for idx, elem := range strings.Split(treePath, "/") {
422
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
426
-
sortFiles(result.Files)
428
-
rp.pages.RepoTree(w, pages.RepoTreeParams{
429
-
LoggedInUser: user,
430
-
BreadCrumbs: breadcrumbs,
431
-
TreePath: treePath,
432
-
RepoInfo: f.RepoInfo(user),
433
-
RepoTreeResponse: result,
437
-
func (rp *Repo) RepoTags(w http.ResponseWriter, r *http.Request) {
438
-
l := rp.logger.With("handler", "RepoTags")
440
-
f, err := rp.repoResolver.Resolve(r)
442
-
l.Error("failed to get repo and knot", "err", err)
447
-
if !rp.config.Core.Dev {
450
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
451
-
xrpcc := &indigoxrpc.Client{
455
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
456
-
xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
457
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
458
-
l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
459
-
rp.pages.Error503(w)
463
-
var result types.RepoTagsResponse
464
-
if err := json.Unmarshal(xrpcBytes, &result); err != nil {
465
-
l.Error("failed to decode XRPC response", "err", err)
466
-
rp.pages.Error503(w)
470
-
artifacts, err := db.GetArtifact(rp.db, db.FilterEq("repo_at", f.RepoAt()))
472
-
l.Error("failed grab artifacts", "err", err)
476
-
// convert artifacts to map for easy UI building
477
-
artifactMap := make(map[plumbing.Hash][]models.Artifact)
478
-
for _, a := range artifacts {
479
-
artifactMap[a.Tag] = append(artifactMap[a.Tag], a)
482
-
var danglingArtifacts []models.Artifact
483
-
for _, a := range artifacts {
485
-
for _, t := range result.Tags {
487
-
if t.Tag.Hash == a.Tag {
494
-
danglingArtifacts = append(danglingArtifacts, a)
498
-
user := rp.oauth.GetUser(r)
499
-
rp.pages.RepoTags(w, pages.RepoTagsParams{
500
-
LoggedInUser: user,
501
-
RepoInfo: f.RepoInfo(user),
502
-
RepoTagsResponse: result,
503
-
ArtifactMap: artifactMap,
504
-
DanglingArtifacts: danglingArtifacts,
508
-
func (rp *Repo) RepoBranches(w http.ResponseWriter, r *http.Request) {
509
-
l := rp.logger.With("handler", "RepoBranches")
511
-
f, err := rp.repoResolver.Resolve(r)
513
-
l.Error("failed to get repo and knot", "err", err)
518
-
if !rp.config.Core.Dev {
521
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
522
-
xrpcc := &indigoxrpc.Client{
526
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
527
-
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
528
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
529
-
l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
530
-
rp.pages.Error503(w)
534
-
var result types.RepoBranchesResponse
535
-
if err := json.Unmarshal(xrpcBytes, &result); err != nil {
536
-
l.Error("failed to decode XRPC response", "err", err)
537
-
rp.pages.Error503(w)
541
-
sortBranches(result.Branches)
543
-
user := rp.oauth.GetUser(r)
544
-
rp.pages.RepoBranches(w, pages.RepoBranchesParams{
545
-
LoggedInUser: user,
546
-
RepoInfo: f.RepoInfo(user),
547
-
RepoBranchesResponse: result,
551
-
func (rp *Repo) DeleteBranch(w http.ResponseWriter, r *http.Request) {
552
-
l := rp.logger.With("handler", "DeleteBranch")
554
-
f, err := rp.repoResolver.Resolve(r)
556
-
l.Error("failed to get repo and knot", "err", err)
560
-
noticeId := "delete-branch-error"
561
-
fail := func(msg string, err error) {
562
-
l.Error(msg, "err", err)
563
-
rp.pages.Notice(w, noticeId, msg)
566
-
branch := r.FormValue("branch")
568
-
fail("No branch provided.", nil)
572
-
client, err := rp.oauth.ServiceClient(
574
-
oauth.WithService(f.Knot),
575
-
oauth.WithLxm(tangled.RepoDeleteBranchNSID),
576
-
oauth.WithDev(rp.config.Core.Dev),
579
-
fail("Failed to connect to knotserver", nil)
583
-
err = tangled.RepoDeleteBranch(
586
-
&tangled.RepoDeleteBranch_Input{
588
-
Repo: f.RepoAt().String(),
591
-
if err := xrpcclient.HandleXrpcErr(err); err != nil {
592
-
fail(fmt.Sprintf("Failed to delete branch: %s", err), err)
595
-
l.Error("deleted branch from knot", "branch", branch, "repo", f.RepoAt())
597
-
rp.pages.HxRefresh(w)
600
-
func (rp *Repo) RepoBlob(w http.ResponseWriter, r *http.Request) {
601
-
l := rp.logger.With("handler", "RepoBlob")
603
-
f, err := rp.repoResolver.Resolve(r)
605
-
l.Error("failed to get repo and knot", "err", err)
609
-
ref := chi.URLParam(r, "ref")
610
-
ref, _ = url.PathUnescape(ref)
612
-
filePath := chi.URLParam(r, "*")
613
-
filePath, _ = url.PathUnescape(filePath)
616
-
if !rp.config.Core.Dev {
619
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
620
-
xrpcc := &indigoxrpc.Client{
624
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
625
-
resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo)
626
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
627
-
l.Error("failed to call XRPC repo.blob", "err", xrpcerr)
628
-
rp.pages.Error503(w)
632
-
// Use XRPC response directly instead of converting to internal types
634
-
var breadcrumbs [][]string
635
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
636
-
if filePath != "" {
637
-
for idx, elem := range strings.Split(filePath, "/") {
638
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
642
-
showRendered := false
643
-
renderToggle := false
645
-
if markup.GetFormat(resp.Path) == markup.FormatMarkdown {
646
-
renderToggle = true
647
-
showRendered = r.URL.Query().Get("code") != "true"
650
-
var unsupported bool
653
-
var contentSrc string
655
-
if resp.IsBinary != nil && *resp.IsBinary {
656
-
ext := strings.ToLower(filepath.Ext(resp.Path))
658
-
case ".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp":
660
-
case ".mp4", ".webm", ".ogg", ".mov", ".avi":
666
-
// fetch the raw binary content using sh.tangled.repo.blob xrpc
667
-
repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
669
-
baseURL := &url.URL{
672
-
Path: "/xrpc/sh.tangled.repo.blob",
674
-
query := baseURL.Query()
675
-
query.Set("repo", repoName)
676
-
query.Set("ref", ref)
677
-
query.Set("path", filePath)
678
-
query.Set("raw", "true")
679
-
baseURL.RawQuery = query.Encode()
680
-
blobURL := baseURL.String()
682
-
contentSrc = blobURL
683
-
if !rp.config.Core.Dev {
684
-
contentSrc = markup.GenerateCamoURL(rp.config.Camo.Host, rp.config.Camo.SharedSecret, blobURL)
689
-
if resp.IsBinary == nil || !*resp.IsBinary {
690
-
lines = strings.Count(resp.Content, "\n") + 1
693
-
var sizeHint uint64
694
-
if resp.Size != nil {
695
-
sizeHint = uint64(*resp.Size)
697
-
sizeHint = uint64(len(resp.Content))
700
-
user := rp.oauth.GetUser(r)
702
-
// Determine if content is binary (dereference pointer)
704
-
if resp.IsBinary != nil {
705
-
isBinary = *resp.IsBinary
708
-
rp.pages.RepoBlob(w, pages.RepoBlobParams{
709
-
LoggedInUser: user,
710
-
RepoInfo: f.RepoInfo(user),
711
-
BreadCrumbs: breadcrumbs,
712
-
ShowRendered: showRendered,
713
-
RenderToggle: renderToggle,
714
-
Unsupported: unsupported,
717
-
ContentSrc: contentSrc,
718
-
RepoBlob_Output: resp,
719
-
Contents: resp.Content,
721
-
SizeHint: sizeHint,
722
-
IsBinary: isBinary,
726
-
func (rp *Repo) RepoBlobRaw(w http.ResponseWriter, r *http.Request) {
727
-
l := rp.logger.With("handler", "RepoBlobRaw")
729
-
f, err := rp.repoResolver.Resolve(r)
731
-
l.Error("failed to get repo and knot", "err", err)
732
-
w.WriteHeader(http.StatusBadRequest)
736
-
ref := chi.URLParam(r, "ref")
737
-
ref, _ = url.PathUnescape(ref)
739
-
filePath := chi.URLParam(r, "*")
740
-
filePath, _ = url.PathUnescape(filePath)
743
-
if !rp.config.Core.Dev {
747
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
748
-
baseURL := &url.URL{
751
-
Path: "/xrpc/sh.tangled.repo.blob",
753
-
query := baseURL.Query()
754
-
query.Set("repo", repo)
755
-
query.Set("ref", ref)
756
-
query.Set("path", filePath)
757
-
query.Set("raw", "true")
758
-
baseURL.RawQuery = query.Encode()
759
-
blobURL := baseURL.String()
761
-
req, err := http.NewRequest("GET", blobURL, nil)
763
-
l.Error("failed to create request", "err", err)
767
-
// forward the If-None-Match header
768
-
if clientETag := r.Header.Get("If-None-Match"); clientETag != "" {
769
-
req.Header.Set("If-None-Match", clientETag)
772
-
client := &http.Client{}
773
-
resp, err := client.Do(req)
775
-
l.Error("failed to reach knotserver", "err", err)
776
-
rp.pages.Error503(w)
779
-
defer resp.Body.Close()
781
-
// forward 304 not modified
782
-
if resp.StatusCode == http.StatusNotModified {
783
-
w.WriteHeader(http.StatusNotModified)
787
-
if resp.StatusCode != http.StatusOK {
788
-
l.Error("knotserver returned non-OK status for raw blob", "url", blobURL, "statuscode", resp.StatusCode)
789
-
w.WriteHeader(resp.StatusCode)
790
-
_, _ = io.Copy(w, resp.Body)
794
-
contentType := resp.Header.Get("Content-Type")
795
-
body, err := io.ReadAll(resp.Body)
797
-
l.Error("error reading response body from knotserver", "err", err)
798
-
w.WriteHeader(http.StatusInternalServerError)
802
-
if strings.HasPrefix(contentType, "text/") || isTextualMimeType(contentType) {
803
-
// serve all textual content as text/plain
804
-
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
806
-
} else if strings.HasPrefix(contentType, "image/") || strings.HasPrefix(contentType, "video/") {
807
-
// serve images and videos with their original content type
808
-
w.Header().Set("Content-Type", contentType)
811
-
w.WriteHeader(http.StatusUnsupportedMediaType)
812
-
w.Write([]byte("unsupported content type"))
// isTextualMimeType returns true if the MIME type represents textual content
818
-
// that should be served as text/plain
819
-
func isTextualMimeType(mimeType string) bool {
820
-
textualTypes := []string{
821
-
"application/json",
823
-
"application/yaml",
824
-
"application/x-yaml",
825
-
"application/toml",
826
-
"application/javascript",
827
-
"application/ecmascript",
831
-
return slices.Contains(textualTypes, mimeType)
// modify the spindle configured for this repo
func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) {
···
SubjectDid: collaboratorIdent.DID,
1551
-
Created: createdAt,
1554
-
fail("Failed to add collaborator.", err)
1560
-
fail("Failed to add collaborator.", err)
1564
-
err = rp.enforcer.E.SavePolicy()
1566
-
fail("Failed to update collaborator permissions.", err)
1570
-
// clear aturi to when everything is successful
1573
-
rp.pages.HxRefresh(w)
1576
-
func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) {
1577
-
user := rp.oauth.GetUser(r)
1578
-
l := rp.logger.With("handler", "DeleteRepo")
1580
-
noticeId := "operation-error"
1581
-
f, err := rp.repoResolver.Resolve(r)
1583
-
l.Error("failed to get repo and knot", "err", err)
1587
-
// remove record from pds
1588
-
atpClient, err := rp.oauth.AuthorizedClient(r)
1590
-
l.Error("failed to get authorized client", "err", err)
1593
-
_, err = comatproto.RepoDeleteRecord(r.Context(), atpClient, &comatproto.RepoDeleteRecord_Input{
1594
-
Collection: tangled.RepoNSID,
1599
-
l.Error("failed to delete record", "err", err)
1600
-
rp.pages.Notice(w, noticeId, "Failed to delete repository from PDS.")
1603
-
l.Info("removed repo record", "aturi", f.RepoAt().String())
1605
-
client, err := rp.oauth.ServiceClient(
1607
-
oauth.WithService(f.Knot),
1608
-
oauth.WithLxm(tangled.RepoDeleteNSID),
1609
-
oauth.WithDev(rp.config.Core.Dev),
1612
-
l.Error("failed to connect to knot server", "err", err)
1616
-
err = tangled.RepoDelete(
1619
-
&tangled.RepoDelete_Input{
1620
-
Did: f.OwnerDid(),
1625
-
if err := xrpcclient.HandleXrpcErr(err); err != nil {
1626
-
rp.pages.Notice(w, noticeId, err.Error())
1629
-
l.Info("deleted repo from knot")
1631
-
tx, err := rp.db.BeginTx(r.Context(), nil)
1633
-
l.Error("failed to start tx")
1634
-
w.Write(fmt.Append(nil, "failed to add collaborator: ", err))
1639
-
err = rp.enforcer.E.LoadPolicy()
1641
-
l.Error("failed to rollback policies")
1645
-
// remove collaborator RBAC
1646
-
repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)
1648
-
rp.pages.Notice(w, noticeId, "Failed to remove collaborators")
1651
-
for _, c := range repoCollaborators {
1653
-
rp.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo())
1655
-
l.Info("removed collaborators")
1657
-
// remove repo RBAC
1658
-
err = rp.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo())
1660
-
rp.pages.Notice(w, noticeId, "Failed to update RBAC rules")
1664
-
// remove repo from db
1665
-
err = db.RemoveRepo(tx, f.OwnerDid(), f.Name)
1667
-
rp.pages.Notice(w, noticeId, "Failed to update appview")
1670
-
l.Info("removed repo from db")
1674
-
l.Error("failed to commit changes", "err", err)
1675
-
http.Error(w, err.Error(), http.StatusInternalServerError)
1679
-
err = rp.enforcer.E.SavePolicy()
1681
-
l.Error("failed to update ACLs", "err", err)
1682
-
http.Error(w, err.Error(), http.StatusInternalServerError)
1686
-
rp.pages.HxRedirect(w, fmt.Sprintf("/%s", f.OwnerDid()))
1689
-
func (rp *Repo) EditBaseSettings(w http.ResponseWriter, r *http.Request) {
1690
-
l := rp.logger.With("handler", "EditBaseSettings")
1692
-
noticeId := "repo-base-settings-error"
1694
-
f, err := rp.repoResolver.Resolve(r)
1696
-
l.Error("failed to get repo and knot", "err", err)
1697
-
w.WriteHeader(http.StatusBadRequest)
1701
-
client, err := rp.oauth.AuthorizedClient(r)
1703
-
l.Error("failed to get client")
1704
-
rp.pages.Notice(w, noticeId, "Failed to update repository information, try again later.")
1709
-
description = r.FormValue("description")
1710
-
website = r.FormValue("website")
1711
-
topicStr = r.FormValue("topics")
1714
-
err = rp.validator.ValidateURI(website)
1716
-
l.Error("invalid uri", "err", err)
1717
-
rp.pages.Notice(w, noticeId, err.Error())
1721
-
topics, err := rp.validator.ValidateRepoTopicStr(topicStr)
1723
-
l.Error("invalid topics", "err", err)
1724
-
rp.pages.Notice(w, noticeId, err.Error())
1727
-
l.Debug("got", "topicsStr", topicStr, "topics", topics)
1730
-
newRepo.Description = description
1731
-
newRepo.Website = website
1732
-
newRepo.Topics = topics
1733
-
record := newRepo.AsRecord()
1735
-
tx, err := rp.db.BeginTx(r.Context(), nil)
1737
-
l.Error("failed to begin transaction", "err", err)
1738
-
rp.pages.Notice(w, noticeId, "Failed to save repository information.")
1741
-
defer tx.Rollback()
1743
-
err = db.PutRepo(tx, newRepo)
1745
-
l.Error("failed to update repository", "err", err)
1746
-
rp.pages.Notice(w, noticeId, "Failed to save repository information.")
1750
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey)
1752
-
// failed to get record
1753
-
l.Error("failed to get repo record", "err", err)
1754
-
rp.pages.Notice(w, noticeId, "Failed to save repository information, no record found on PDS.")
1757
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1758
-
Collection: tangled.RepoNSID,
1759
-
Repo: newRepo.Did,
1760
-
Rkey: newRepo.Rkey,
1761
-
SwapRecord: ex.Cid,
1762
-
Record: &lexutil.LexiconTypeDecoder{
800
+
Created: createdAt,
1768
-
l.Error("failed to perferom update-repo query", "err", err)
1769
-
// failed to get record
1770
-
rp.pages.Notice(w, noticeId, "Failed to save repository information, unable to save to PDS.")
803
+
fail("Failed to add collaborator.", err)
1776
-
l.Error("failed to commit", "err", err)
809
+
fail("Failed to add collaborator.", err)
813
+
err = rp.enforcer.E.SavePolicy()
815
+
fail("Failed to update collaborator permissions.", err)
819
+
// clear aturi to when everything is successful
1782
-
func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
1783
-
l := rp.logger.With("handler", "SetDefaultBranch")
825
+
func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) {
826
+
user := rp.oauth.GetUser(r)
827
+
l := rp.logger.With("handler", "DeleteRepo")
829
+
noticeId := "operation-error"
f, err := rp.repoResolver.Resolve(r)
l.Error("failed to get repo and knot", "err", err)
1791
-
noticeId := "operation-error"
1792
-
branch := r.FormValue("branch")
1794
-
http.Error(w, "malformed form", http.StatusBadRequest)
836
+
// remove record from pds
837
+
atpClient, err := rp.oauth.AuthorizedClient(r)
839
+
l.Error("failed to get authorized client", "err", err)
842
+
_, err = comatproto.RepoDeleteRecord(r.Context(), atpClient, &comatproto.RepoDeleteRecord_Input{
843
+
Collection: tangled.RepoNSID,
848
+
l.Error("failed to delete record", "err", err)
849
+
rp.pages.Notice(w, noticeId, "Failed to delete repository from PDS.")
852
+
l.Info("removed repo record", "aturi", f.RepoAt().String())
client, err := rp.oauth.ServiceClient(
oauth.WithService(f.Knot),
1801
-
oauth.WithLxm(tangled.RepoSetDefaultBranchNSID),
857
+
oauth.WithLxm(tangled.RepoDeleteNSID),
oauth.WithDev(rp.config.Core.Dev),
l.Error("failed to connect to knot server", "err", err)
1806
-
rp.pages.Notice(w, noticeId, "Failed to connect to knot server.")
1810
-
xe := tangled.RepoSetDefaultBranch(
865
+
err = tangled.RepoDelete(
1813
-
&tangled.RepoSetDefaultBranch_Input{
1814
-
Repo: f.RepoAt().String(),
1815
-
DefaultBranch: branch,
868
+
&tangled.RepoDelete_Input{
1818
-
if err := xrpcclient.HandleXrpcErr(xe); err != nil {
1819
-
l.Error("xrpc failed", "err", xe)
874
+
if err := xrpcclient.HandleXrpcErr(err); err != nil {
rp.pages.Notice(w, noticeId, err.Error())
878
+
l.Info("deleted repo from knot")
1824
-
rp.pages.HxRefresh(w)
1827
-
func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) {
1828
-
user := rp.oauth.GetUser(r)
1829
-
l := rp.logger.With("handler", "Secrets")
1830
-
l = l.With("did", user.Did)
1832
-
f, err := rp.repoResolver.Resolve(r)
1834
-
l.Error("failed to get repo and knot", "err", err)
1838
-
if f.Spindle == "" {
1839
-
l.Error("empty spindle cannot add/rm secret", "err", err)
1843
-
lxm := tangled.RepoAddSecretNSID
1844
-
if r.Method == http.MethodDelete {
1845
-
lxm = tangled.RepoRemoveSecretNSID
1848
-
spindleClient, err := rp.oauth.ServiceClient(
1850
-
oauth.WithService(f.Spindle),
1851
-
oauth.WithLxm(lxm),
1852
-
oauth.WithExp(60),
1853
-
oauth.WithDev(rp.config.Core.Dev),
880
+
tx, err := rp.db.BeginTx(r.Context(), nil)
1856
-
l.Error("failed to create spindle client", "err", err)
1860
-
key := r.FormValue("key")
1862
-
w.WriteHeader(http.StatusBadRequest)
882
+
l.Error("failed to start tx")
883
+
w.Write(fmt.Append(nil, "failed to add collaborator: ", err))
1867
-
case http.MethodPut:
1868
-
errorId := "add-secret-error"
1870
-
value := r.FormValue("value")
1872
-
w.WriteHeader(http.StatusBadRequest)
1876
-
err = tangled.RepoAddSecret(
1879
-
&tangled.RepoAddSecret_Input{
1880
-
Repo: f.RepoAt().String(),
1886
-
l.Error("Failed to add secret.", "err", err)
1887
-
rp.pages.Notice(w, errorId, "Failed to add secret.")
1891
-
case http.MethodDelete:
1892
-
errorId := "operation-error"
1894
-
err = tangled.RepoRemoveSecret(
1897
-
&tangled.RepoRemoveSecret_Input{
1898
-
Repo: f.RepoAt().String(),
888
+
err = rp.enforcer.E.LoadPolicy()
1903
-
l.Error("Failed to delete secret.", "err", err)
1904
-
rp.pages.Notice(w, errorId, "Failed to delete secret.")
890
+
l.Error("failed to rollback policies")
1909
-
rp.pages.HxRefresh(w)
1912
-
type tab = map[string]any
1915
-
// would be great to have ordered maps right about now
1916
-
settingsTabs []tab = []tab{
1917
-
{"Name": "general", "Icon": "sliders-horizontal"},
1918
-
{"Name": "access", "Icon": "users"},
1919
-
{"Name": "pipelines", "Icon": "layers-2"},
1923
-
func (rp *Repo) RepoSettings(w http.ResponseWriter, r *http.Request) {
1924
-
tabVal := r.URL.Query().Get("tab")
1926
-
tabVal = "general"
1931
-
rp.generalSettings(w, r)
1934
-
rp.accessSettings(w, r)
1937
-
rp.pipelineSettings(w, r)
1941
-
func (rp *Repo) generalSettings(w http.ResponseWriter, r *http.Request) {
1942
-
l := rp.logger.With("handler", "generalSettings")
1944
-
f, err := rp.repoResolver.Resolve(r)
1945
-
user := rp.oauth.GetUser(r)
1948
-
if !rp.config.Core.Dev {
1951
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
1952
-
xrpcc := &indigoxrpc.Client{
1956
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1957
-
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1958
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1959
-
l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
1960
-
rp.pages.Error503(w)
894
+
// remove collaborator RBAC
895
+
repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)
897
+
rp.pages.Notice(w, noticeId, "Failed to remove collaborators")
1964
-
var result types.RepoBranchesResponse
1965
-
if err := json.Unmarshal(xrpcBytes, &result); err != nil {
1966
-
l.Error("failed to decode XRPC response", "err", err)
1967
-
rp.pages.Error503(w)
900
+
for _, c := range repoCollaborators {
902
+
rp.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo())
904
+
l.Info("removed collaborators")
1971
-
defaultLabels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs))
906
+
// remove repo RBAC
907
+
err = rp.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo())
1973
-
l.Error("failed to fetch labels", "err", err)
1974
-
rp.pages.Error503(w)
909
+
rp.pages.Notice(w, noticeId, "Failed to update RBAC rules")
1978
-
labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels))
913
+
// remove repo from db
914
+
err = db.RemoveRepo(tx, f.OwnerDid(), f.Name)
1980
-
l.Error("failed to fetch labels", "err", err)
1981
-
rp.pages.Error503(w)
916
+
rp.pages.Notice(w, noticeId, "Failed to update appview")
1984
-
// remove default labels from the labels list, if present
1985
-
defaultLabelMap := make(map[string]bool)
1986
-
for _, dl := range defaultLabels {
1987
-
defaultLabelMap[dl.AtUri().String()] = true
1990
-
for _, l := range labels {
1991
-
if !defaultLabelMap[l.AtUri().String()] {
1996
-
labels = labels[:n]
1998
-
subscribedLabels := make(map[string]struct{})
1999
-
for _, l := range f.Repo.Labels {
2000
-
subscribedLabels[l] = struct{}{}
2003
-
// if there is atleast 1 unsubbed default label, show the "subscribe all" button,
2004
-
// if all default labels are subbed, show the "unsubscribe all" button
2005
-
shouldSubscribeAll := false
2006
-
for _, dl := range defaultLabels {
2007
-
if _, ok := subscribedLabels[dl.AtUri().String()]; !ok {
2008
-
// one of the default labels is not subscribed to
2009
-
shouldSubscribeAll = true
2014
-
rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{
2015
-
LoggedInUser: user,
2016
-
RepoInfo: f.RepoInfo(user),
2017
-
Branches: result.Branches,
2019
-
DefaultLabels: defaultLabels,
2020
-
SubscribedLabels: subscribedLabels,
2021
-
ShouldSubscribeAll: shouldSubscribeAll,
2022
-
Tabs: settingsTabs,
2027
-
func (rp *Repo) accessSettings(w http.ResponseWriter, r *http.Request) {
2028
-
l := rp.logger.With("handler", "accessSettings")
2030
-
f, err := rp.repoResolver.Resolve(r)
2031
-
user := rp.oauth.GetUser(r)
919
+
l.Info("removed repo from db")
2033
-
repoCollaborators, err := f.Collaborators(r.Context())
2035
-
l.Error("failed to get collaborators", "err", err)
923
+
l.Error("failed to commit changes", "err", err)
924
+
http.Error(w, err.Error(), http.StatusInternalServerError)
2038
-
rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{
2039
-
LoggedInUser: user,
2040
-
RepoInfo: f.RepoInfo(user),
2041
-
Tabs: settingsTabs,
2043
-
Collaborators: repoCollaborators,
2047
-
func (rp *Repo) pipelineSettings(w http.ResponseWriter, r *http.Request) {
2048
-
l := rp.logger.With("handler", "pipelineSettings")
2050
-
f, err := rp.repoResolver.Resolve(r)
2051
-
user := rp.oauth.GetUser(r)
2053
-
// all spindles that the repo owner is a member of
2054
-
spindles, err := rp.enforcer.GetSpindlesForUser(f.OwnerDid())
928
+
err = rp.enforcer.E.SavePolicy()
2056
-
l.Error("failed to fetch spindles", "err", err)
930
+
l.Error("failed to update ACLs", "err", err)
931
+
http.Error(w, err.Error(), http.StatusInternalServerError)
2060
-
var secrets []*tangled.RepoListSecrets_Secret
2061
-
if f.Spindle != "" {
2062
-
if spindleClient, err := rp.oauth.ServiceClient(
2064
-
oauth.WithService(f.Spindle),
2065
-
oauth.WithLxm(tangled.RepoListSecretsNSID),
2066
-
oauth.WithExp(60),
2067
-
oauth.WithDev(rp.config.Core.Dev),
2069
-
l.Error("failed to create spindle client", "err", err)
2070
-
} else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt().String()); err != nil {
2071
-
l.Error("failed to fetch secrets", "err", err)
2073
-
secrets = resp.Secrets
2077
-
slices.SortFunc(secrets, func(a, b *tangled.RepoListSecrets_Secret) int {
2078
-
return strings.Compare(a.Key, b.Key)
2082
-
for _, s := range secrets {
2083
-
dids = append(dids, s.CreatedBy)
2085
-
resolvedIdents := rp.idResolver.ResolveIdents(r.Context(), dids)
2087
-
// convert to a more manageable form
2088
-
var niceSecret []map[string]any
2089
-
for id, s := range secrets {
2090
-
when, _ := time.Parse(time.RFC3339, s.CreatedAt)
2091
-
niceSecret = append(niceSecret, map[string]any{
2094
-
"CreatedAt": when,
2095
-
"CreatedBy": resolvedIdents[id].Handle.String(),
2099
-
rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{
2100
-
LoggedInUser: user,
2101
-
RepoInfo: f.RepoInfo(user),
2102
-
Tabs: settingsTabs,
2104
-
Spindles: spindles,
2105
-
CurrentSpindle: f.Spindle,
2106
-
Secrets: niceSecret,
935
+
rp.pages.HxRedirect(w, fmt.Sprintf("/%s", f.OwnerDid()))
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
···
2392
-
func (rp *Repo) RepoCompareNew(w http.ResponseWriter, r *http.Request) {
2393
-
l := rp.logger.With("handler", "RepoCompareNew")
2395
-
user := rp.oauth.GetUser(r)
2396
-
f, err := rp.repoResolver.Resolve(r)
2398
-
l.Error("failed to get repo and knot", "err", err)
2403
-
if !rp.config.Core.Dev {
2406
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
2407
-
xrpcc := &indigoxrpc.Client{
2411
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
2412
-
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
2413
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
2414
-
l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
2415
-
rp.pages.Error503(w)
2419
-
var branchResult types.RepoBranchesResponse
2420
-
if err := json.Unmarshal(branchBytes, &branchResult); err != nil {
2421
-
l.Error("failed to decode XRPC branches response", "err", err)
2422
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2425
-
branches := branchResult.Branches
2427
-
sortBranches(branches)
2429
-
var defaultBranch string
2430
-
for _, b := range branches {
2432
-
defaultBranch = b.Name
2436
-
base := defaultBranch
2437
-
head := defaultBranch
2439
-
params := r.URL.Query()
2440
-
queryBase := params.Get("base")
2441
-
queryHead := params.Get("head")
2442
-
if queryBase != "" {
2445
-
if queryHead != "" {
2449
-
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
2450
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
2451
-
l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
2452
-
rp.pages.Error503(w)
2456
-
var tags types.RepoTagsResponse
2457
-
if err := json.Unmarshal(tagBytes, &tags); err != nil {
2458
-
l.Error("failed to decode XRPC tags response", "err", err)
2459
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2463
-
repoinfo := f.RepoInfo(user)
2465
-
rp.pages.RepoCompareNew(w, pages.RepoCompareNewParams{
2466
-
LoggedInUser: user,
2467
-
RepoInfo: repoinfo,
2468
-
Branches: branches,
2475
-
func (rp *Repo) RepoCompare(w http.ResponseWriter, r *http.Request) {
2476
-
l := rp.logger.With("handler", "RepoCompare")
2478
-
user := rp.oauth.GetUser(r)
2479
-
f, err := rp.repoResolver.Resolve(r)
2481
-
l.Error("failed to get repo and knot", "err", err)
2485
-
var diffOpts types.DiffOpts
2486
-
if d := r.URL.Query().Get("diff"); d == "split" {
2487
-
diffOpts.Split = true
2490
-
// if user is navigating to one of
2491
-
// /compare/{base}/{head}
2492
-
// /compare/{base}...{head}
2493
-
base := chi.URLParam(r, "base")
2494
-
head := chi.URLParam(r, "head")
2495
-
if base == "" && head == "" {
2496
-
rest := chi.URLParam(r, "*") // master...feature/xyz
2497
-
parts := strings.SplitN(rest, "...", 2)
2498
-
if len(parts) == 2 {
2504
-
base, _ = url.PathUnescape(base)
2505
-
head, _ = url.PathUnescape(head)
2507
-
if base == "" || head == "" {
2508
-
l.Error("invalid comparison")
2509
-
rp.pages.Error404(w)
2514
-
if !rp.config.Core.Dev {
2517
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
2518
-
xrpcc := &indigoxrpc.Client{
2522
-
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
2524
-
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
2525
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
2526
-
l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
2527
-
rp.pages.Error503(w)
2531
-
var branches types.RepoBranchesResponse
2532
-
if err := json.Unmarshal(branchBytes, &branches); err != nil {
2533
-
l.Error("failed to decode XRPC branches response", "err", err)
2534
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2538
-
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
2539
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
2540
-
l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
2541
-
rp.pages.Error503(w)
2545
-
var tags types.RepoTagsResponse
2546
-
if err := json.Unmarshal(tagBytes, &tags); err != nil {
2547
-
l.Error("failed to decode XRPC tags response", "err", err)
2548
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2552
-
compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
2553
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
2554
-
l.Error("failed to call XRPC repo.compare", "err", xrpcerr)
2555
-
rp.pages.Error503(w)
2559
-
var formatPatch types.RepoFormatPatchResponse
2560
-
if err := json.Unmarshal(compareBytes, &formatPatch); err != nil {
2561
-
l.Error("failed to decode XRPC compare response", "err", err)
2562
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2566
-
var diff types.NiceDiff
2567
-
if formatPatch.CombinedPatchRaw != "" {
2568
-
diff = patchutil.AsNiceDiff(formatPatch.CombinedPatchRaw, base)
2570
-
diff = patchutil.AsNiceDiff(formatPatch.FormatPatchRaw, base)
2573
-
repoinfo := f.RepoInfo(user)
2575
-
rp.pages.RepoCompare(w, pages.RepoCompareParams{
2576
-
LoggedInUser: user,
2577
-
RepoInfo: repoinfo,
2578
-
Branches: branches.Branches,
2583
-
DiffOpts: diffOpts,