+4
-8
appview/db/issues.go
+4
-8
appview/db/issues.go
···
359
359
repoMap[string(repos[i].RepoAt())] = &repos[i]
360
360
}
361
361
362
-
for issueAt, i := range issueMap {
363
-
if r, ok := repoMap[string(i.RepoAt)]; ok {
364
-
i.Repo = r
365
-
} else {
366
-
// do not show up the issue if the repo is deleted
367
-
// TODO: foreign key where?
368
-
delete(issueMap, issueAt)
369
-
}
362
+
for issueAt := range issueMap {
363
+
i := issueMap[issueAt]
364
+
r := repoMap[string(i.RepoAt)]
365
+
i.Repo = r
370
366
}
371
367
372
368
// collect comments
+3
-3
appview/pages/templates/repo/tree.html
+3
-3
appview/pages/templates/repo/tree.html
···
25
25
<div class="flex flex-col md:flex-row md:justify-between gap-2">
26
26
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
27
27
{{ range .BreadCrumbs }}
28
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
28
+
<a href="{{ index . 1 }}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
29
29
{{ end }}
30
30
</div>
31
31
<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">
32
32
{{ $stats := .TreeStats }}
33
33
34
-
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}">{{ $.Ref }}</a></span>
34
+
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ pathEscape $.Ref }}">{{ $.Ref }}</a></span>
35
35
{{ if eq $stats.NumFolders 1 }}
36
36
<span class="select-none px-1 md:px-2 [&:before]:content-['ยท']"></span>
37
37
<span>{{ $stats.NumFolders }} folder</span>
···
55
55
{{ range .Files }}
56
56
<div class="grid grid-cols-12 gap-4 items-center py-1">
57
57
<div class="col-span-8 md:col-span-4">
58
-
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (urlquery $.Ref) $.TreePath .Name }}
58
+
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (pathEscape $.Ref) $.TreePath .Name }}
59
59
{{ $icon := "folder" }}
60
60
{{ $iconStyle := "size-4 fill-current" }}
61
61
+16
-17
appview/repo/index.go
+16
-17
appview/repo/index.go
···
5
5
"fmt"
6
6
"log"
7
7
"net/http"
8
+
"net/url"
8
9
"slices"
9
10
"sort"
10
11
"strings"
···
31
32
32
33
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
33
34
ref := chi.URLParam(r, "ref")
35
+
ref, _ = url.PathUnescape(ref)
34
36
35
37
f, err := rp.repoResolver.Resolve(r)
36
38
if err != nil {
···
61
63
RepoInfo: repoInfo,
62
64
})
63
65
return
64
-
} else {
65
-
rp.pages.Error503(w)
66
-
log.Println("failed to build index response", err)
67
-
return
68
66
}
67
+
68
+
rp.pages.Error503(w)
69
+
log.Println("failed to build index response", err)
70
+
return
69
71
}
70
72
71
73
tagMap := make(map[string][]string)
···
245
247
// first get branches to determine the ref if not specified
246
248
branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repo)
247
249
if err != nil {
248
-
return nil, err
250
+
return nil, fmt.Errorf("failed to call repoBranches: %w", err)
249
251
}
250
252
251
253
var branchesResp types.RepoBranchesResponse
252
254
if err := json.Unmarshal(branchesBytes, &branchesResp); err != nil {
253
-
return nil, err
255
+
return nil, fmt.Errorf("failed to unmarshal branches response: %w", err)
254
256
}
255
257
256
258
// if no ref specified, use default branch or first available
257
-
if ref == "" && len(branchesResp.Branches) > 0 {
259
+
if ref == "" {
258
260
for _, branch := range branchesResp.Branches {
259
261
if branch.IsDefault {
260
262
ref = branch.Name
261
263
break
262
264
}
263
265
}
264
-
if ref == "" {
265
-
ref = branchesResp.Branches[0].Name
266
-
}
267
266
}
268
267
269
-
// check if repo is empty
270
-
if len(branchesResp.Branches) == 0 {
268
+
// if ref is still empty, this means the default branch is not set
269
+
if ref == "" {
271
270
return &types.RepoIndexResponse{
272
271
IsEmpty: true,
273
272
Branches: branchesResp.Branches,
···
292
291
defer wg.Done()
293
292
tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo)
294
293
if err != nil {
295
-
errs = errors.Join(errs, err)
294
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTags: %w", err))
296
295
return
297
296
}
298
297
299
298
if err := json.Unmarshal(tagsBytes, &tagsResp); err != nil {
300
-
errs = errors.Join(errs, err)
299
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoTags: %w", err))
301
300
}
302
301
}()
303
302
···
307
306
defer wg.Done()
308
307
resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repo)
309
308
if err != nil {
310
-
errs = errors.Join(errs, err)
309
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTree: %w", err))
311
310
return
312
311
}
313
312
treeResp = resp
···
319
318
defer wg.Done()
320
319
logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repo)
321
320
if err != nil {
322
-
errs = errors.Join(errs, err)
321
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoLog: %w", err))
323
322
return
324
323
}
325
324
326
325
if err := json.Unmarshal(logBytes, &logResp); err != nil {
327
-
errs = errors.Join(errs, err)
326
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoLog: %w", err))
328
327
}
329
328
}()
330
329
+97
-118
appview/repo/repo.go
+97
-118
appview/repo/repo.go
···
85
85
}
86
86
87
87
func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
88
-
refParam := chi.URLParam(r, "ref")
88
+
ref := chi.URLParam(r, "ref")
89
+
ref, _ = url.PathUnescape(ref)
90
+
89
91
f, err := rp.repoResolver.Resolve(r)
90
92
if err != nil {
91
93
log.Println("failed to get repo and knot", err)
···
102
104
}
103
105
104
106
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
105
-
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", refParam, repo)
106
-
if err != nil {
107
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
108
-
log.Println("failed to call XRPC repo.archive", xrpcerr)
109
-
rp.pages.Error503(w)
110
-
return
111
-
}
112
-
rp.pages.Error404(w)
107
+
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo)
108
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
109
+
log.Println("failed to call XRPC repo.archive", xrpcerr)
110
+
rp.pages.Error503(w)
113
111
return
114
112
}
115
113
116
-
// Set headers for file download
117
-
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, refParam)
114
+
// Set headers for file download, just pass along whatever the knot specifies
115
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
116
+
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename)
118
117
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
119
118
w.Header().Set("Content-Type", "application/gzip")
120
119
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
···
139
138
}
140
139
141
140
ref := chi.URLParam(r, "ref")
141
+
ref, _ = url.PathUnescape(ref)
142
142
143
143
scheme := "http"
144
144
if !rp.config.Core.Dev {
···
159
159
160
160
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
161
161
xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo)
162
-
if err != nil {
163
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
164
-
log.Println("failed to call XRPC repo.log", xrpcerr)
165
-
rp.pages.Error503(w)
166
-
return
167
-
}
168
-
rp.pages.Error404(w)
162
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
163
+
log.Println("failed to call XRPC repo.log", xrpcerr)
164
+
rp.pages.Error503(w)
169
165
return
170
166
}
171
167
···
177
173
}
178
174
179
175
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
180
-
if err != nil {
181
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
182
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
183
-
rp.pages.Error503(w)
184
-
return
185
-
}
176
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
177
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
178
+
rp.pages.Error503(w)
179
+
return
186
180
}
187
181
188
182
tagMap := make(map[string][]string)
···
196
190
}
197
191
198
192
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
199
-
if err != nil {
200
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
201
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
202
-
rp.pages.Error503(w)
203
-
return
204
-
}
193
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
194
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
195
+
rp.pages.Error503(w)
196
+
return
205
197
}
206
198
207
199
if branchBytes != nil {
···
353
345
return
354
346
}
355
347
ref := chi.URLParam(r, "ref")
348
+
ref, _ = url.PathUnescape(ref)
356
349
357
350
var diffOpts types.DiffOpts
358
351
if d := r.URL.Query().Get("diff"); d == "split" {
···
375
368
376
369
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
377
370
xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo)
378
-
if err != nil {
379
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
380
-
log.Println("failed to call XRPC repo.diff", xrpcerr)
381
-
rp.pages.Error503(w)
382
-
return
383
-
}
384
-
rp.pages.Error404(w)
371
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
372
+
log.Println("failed to call XRPC repo.diff", xrpcerr)
373
+
rp.pages.Error503(w)
385
374
return
386
375
}
387
376
···
433
422
}
434
423
435
424
ref := chi.URLParam(r, "ref")
436
-
treePath := chi.URLParam(r, "*")
425
+
ref, _ = url.PathUnescape(ref)
437
426
438
427
// if the tree path has a trailing slash, let's strip it
439
428
// so we don't 404
429
+
treePath := chi.URLParam(r, "*")
430
+
treePath, _ = url.PathUnescape(treePath)
440
431
treePath = strings.TrimSuffix(treePath, "/")
441
432
442
433
scheme := "http"
···
450
441
451
442
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
452
443
xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo)
453
-
if err != nil {
454
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
455
-
log.Println("failed to call XRPC repo.tree", xrpcerr)
456
-
rp.pages.Error503(w)
457
-
return
458
-
}
459
-
rp.pages.Error404(w)
444
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
445
+
log.Println("failed to call XRPC repo.tree", xrpcerr)
446
+
rp.pages.Error503(w)
460
447
return
461
448
}
462
449
···
498
485
499
486
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
500
487
// so we can safely redirect to the "parent" (which is the same file).
501
-
unescapedTreePath, _ := url.PathUnescape(treePath)
502
-
if len(result.Files) == 0 && result.Parent == unescapedTreePath {
503
-
http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)
488
+
if len(result.Files) == 0 && result.Parent == treePath {
489
+
redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent)
490
+
http.Redirect(w, r, redirectTo, http.StatusFound)
504
491
return
505
492
}
506
493
507
494
user := rp.oauth.GetUser(r)
508
495
509
496
var breadcrumbs [][]string
510
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
497
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
511
498
if treePath != "" {
512
499
for idx, elem := range strings.Split(treePath, "/") {
513
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
500
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
514
501
}
515
502
}
516
503
···
543
530
544
531
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
545
532
xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
546
-
if err != nil {
547
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
548
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
549
-
rp.pages.Error503(w)
550
-
return
551
-
}
552
-
rp.pages.Error404(w)
533
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
534
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
535
+
rp.pages.Error503(w)
553
536
return
554
537
}
555
538
···
616
599
617
600
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
618
601
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
619
-
if err != nil {
620
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
621
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
622
-
rp.pages.Error503(w)
623
-
return
624
-
}
625
-
rp.pages.Error404(w)
602
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
603
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
604
+
rp.pages.Error503(w)
626
605
return
627
606
}
628
607
···
651
630
}
652
631
653
632
ref := chi.URLParam(r, "ref")
633
+
ref, _ = url.PathUnescape(ref)
634
+
654
635
filePath := chi.URLParam(r, "*")
636
+
filePath, _ = url.PathUnescape(filePath)
655
637
656
638
scheme := "http"
657
639
if !rp.config.Core.Dev {
···
664
646
665
647
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
666
648
resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo)
667
-
if err != nil {
668
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
669
-
log.Println("failed to call XRPC repo.blob", xrpcerr)
670
-
rp.pages.Error503(w)
671
-
return
672
-
}
673
-
rp.pages.Error404(w)
649
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
650
+
log.Println("failed to call XRPC repo.blob", xrpcerr)
651
+
rp.pages.Error503(w)
674
652
return
675
653
}
676
654
677
655
// Use XRPC response directly instead of converting to internal types
678
656
679
657
var breadcrumbs [][]string
680
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
658
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
681
659
if filePath != "" {
682
660
for idx, elem := range strings.Split(filePath, "/") {
683
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
661
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
684
662
}
685
663
}
686
664
···
710
688
711
689
// fetch the raw binary content using sh.tangled.repo.blob xrpc
712
690
repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
713
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
714
-
scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath))
691
+
692
+
baseURL := &url.URL{
693
+
Scheme: scheme,
694
+
Host: f.Knot,
695
+
Path: "/xrpc/sh.tangled.repo.blob",
696
+
}
697
+
query := baseURL.Query()
698
+
query.Set("repo", repoName)
699
+
query.Set("ref", ref)
700
+
query.Set("path", filePath)
701
+
query.Set("raw", "true")
702
+
baseURL.RawQuery = query.Encode()
703
+
blobURL := baseURL.String()
715
704
716
705
contentSrc = blobURL
717
706
if !rp.config.Core.Dev {
···
766
755
}
767
756
768
757
ref := chi.URLParam(r, "ref")
758
+
ref, _ = url.PathUnescape(ref)
759
+
769
760
filePath := chi.URLParam(r, "*")
761
+
filePath, _ = url.PathUnescape(filePath)
770
762
771
763
scheme := "http"
772
764
if !rp.config.Core.Dev {
···
774
766
}
775
767
776
768
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
777
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
778
-
scheme, f.Knot, url.QueryEscape(repo), url.QueryEscape(ref), url.QueryEscape(filePath))
769
+
baseURL := &url.URL{
770
+
Scheme: scheme,
771
+
Host: f.Knot,
772
+
Path: "/xrpc/sh.tangled.repo.blob",
773
+
}
774
+
query := baseURL.Query()
775
+
query.Set("repo", repo)
776
+
query.Set("ref", ref)
777
+
query.Set("path", filePath)
778
+
query.Set("raw", "true")
779
+
baseURL.RawQuery = query.Encode()
780
+
blobURL := baseURL.String()
779
781
780
782
req, err := http.NewRequest("GET", blobURL, nil)
781
783
if err != nil {
···
1364
1366
1365
1367
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1366
1368
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1367
-
if err != nil {
1368
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1369
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1370
-
rp.pages.Error503(w)
1371
-
return
1372
-
}
1369
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1370
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1373
1371
rp.pages.Error503(w)
1374
1372
return
1375
1373
}
···
1471
1469
1472
1470
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1473
1471
ref := chi.URLParam(r, "ref")
1472
+
ref, _ = url.PathUnescape(ref)
1474
1473
1475
1474
user := rp.oauth.GetUser(r)
1476
1475
f, err := rp.repoResolver.Resolve(r)
···
1759
1758
1760
1759
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1761
1760
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1762
-
if err != nil {
1763
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1764
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1765
-
rp.pages.Error503(w)
1766
-
return
1767
-
}
1768
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1761
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1762
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1763
+
rp.pages.Error503(w)
1769
1764
return
1770
1765
}
1771
1766
···
1800
1795
}
1801
1796
1802
1797
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
1803
-
if err != nil {
1804
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1805
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
1806
-
rp.pages.Error503(w)
1807
-
return
1808
-
}
1809
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1798
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1799
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
1800
+
rp.pages.Error503(w)
1810
1801
return
1811
1802
}
1812
1803
···
1877
1868
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1878
1869
1879
1870
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1880
-
if err != nil {
1881
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1882
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1883
-
rp.pages.Error503(w)
1884
-
return
1885
-
}
1886
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1871
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1872
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1873
+
rp.pages.Error503(w)
1887
1874
return
1888
1875
}
1889
1876
···
1895
1882
}
1896
1883
1897
1884
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
1898
-
if err != nil {
1899
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1900
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
1901
-
rp.pages.Error503(w)
1902
-
return
1903
-
}
1904
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1885
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1886
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
1887
+
rp.pages.Error503(w)
1905
1888
return
1906
1889
}
1907
1890
···
1913
1896
}
1914
1897
1915
1898
compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
1916
-
if err != nil {
1917
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1918
-
log.Println("failed to call XRPC repo.compare", xrpcerr)
1919
-
rp.pages.Error503(w)
1920
-
return
1921
-
}
1922
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1899
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1900
+
log.Println("failed to call XRPC repo.compare", xrpcerr)
1901
+
rp.pages.Error503(w)
1923
1902
return
1924
1903
}
1925
1904
+1
-10
knotserver/xrpc/list_keys.go
+1
-10
knotserver/xrpc/list_keys.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
5
"strconv"
7
6
···
46
45
response.Cursor = &nextCursor
47
46
}
48
47
49
-
w.Header().Set("Content-Type", "application/json")
50
-
if err := json.NewEncoder(w).Encode(response); err != nil {
51
-
x.Logger.Error("failed to encode response", "error", err)
52
-
writeError(w, xrpcerr.NewXrpcError(
53
-
xrpcerr.WithTag("InternalServerError"),
54
-
xrpcerr.WithMessage("failed to encode response"),
55
-
), http.StatusInternalServerError)
56
-
return
57
-
}
48
+
writeJson(w, response)
58
49
}
+1
-10
knotserver/xrpc/owner.go
+1
-10
knotserver/xrpc/owner.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
5
7
6
"tangled.sh/tangled.sh/core/api/tangled"
···
19
18
Owner: owner,
20
19
}
21
20
22
-
w.Header().Set("Content-Type", "application/json")
23
-
if err := json.NewEncoder(w).Encode(response); err != nil {
24
-
x.Logger.Error("failed to encode response", "error", err)
25
-
writeError(w, xrpcerr.NewXrpcError(
26
-
xrpcerr.WithTag("InternalServerError"),
27
-
xrpcerr.WithMessage("failed to encode response"),
28
-
), http.StatusInternalServerError)
29
-
return
30
-
}
21
+
writeJson(w, response)
31
22
}
+8
-7
knotserver/xrpc/repo_archive.go
+8
-7
knotserver/xrpc/repo_archive.go
···
13
13
)
14
14
15
15
func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) {
16
-
repo, repoPath, unescapedRef, err := x.parseStandardParams(r)
16
+
repo := r.URL.Query().Get("repo")
17
+
repoPath, err := x.parseRepoParam(repo)
17
18
if err != nil {
18
19
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
19
20
return
20
21
}
22
+
23
+
ref := r.URL.Query().Get("ref")
24
+
// ref can be empty (git.Open handles this)
21
25
22
26
format := r.URL.Query().Get("format")
23
27
if format == "" {
···
34
38
return
35
39
}
36
40
37
-
gr, err := git.Open(repoPath, unescapedRef)
41
+
gr, err := git.Open(repoPath, ref)
38
42
if err != nil {
39
-
writeError(w, xrpcerr.NewXrpcError(
40
-
xrpcerr.WithTag("RefNotFound"),
41
-
xrpcerr.WithMessage("repository or ref not found"),
42
-
), http.StatusNotFound)
43
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
43
44
return
44
45
}
45
46
46
47
repoParts := strings.Split(repo, "/")
47
48
repoName := repoParts[len(repoParts)-1]
48
49
49
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
50
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
50
51
51
52
var archivePrefix string
52
53
if prefix != "" {
+7
-15
knotserver/xrpc/repo_blob.go
+7
-15
knotserver/xrpc/repo_blob.go
···
3
3
import (
4
4
"crypto/sha256"
5
5
"encoding/base64"
6
-
"encoding/json"
7
6
"fmt"
8
7
"net/http"
9
8
"path/filepath"
···
16
15
)
17
16
18
17
func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) {
19
-
_, repoPath, ref, err := x.parseStandardParams(r)
18
+
repo := r.URL.Query().Get("repo")
19
+
repoPath, err := x.parseRepoParam(repo)
20
20
if err != nil {
21
21
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
22
22
return
23
23
}
24
24
25
+
ref := r.URL.Query().Get("ref")
26
+
// ref can be empty (git.Open handles this)
27
+
25
28
treePath := r.URL.Query().Get("path")
26
29
if treePath == "" {
27
30
writeError(w, xrpcerr.NewXrpcError(
···
35
38
36
39
gr, err := git.Open(repoPath, ref)
37
40
if err != nil {
38
-
writeError(w, xrpcerr.NewXrpcError(
39
-
xrpcerr.WithTag("RefNotFound"),
40
-
xrpcerr.WithMessage("repository or ref not found"),
41
-
), http.StatusNotFound)
41
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
42
42
return
43
43
}
44
44
···
123
123
response.MimeType = &mimeType
124
124
}
125
125
126
-
w.Header().Set("Content-Type", "application/json")
127
-
if err := json.NewEncoder(w).Encode(response); err != nil {
128
-
x.Logger.Error("failed to encode response", "error", err)
129
-
writeError(w, xrpcerr.NewXrpcError(
130
-
xrpcerr.WithTag("InternalServerError"),
131
-
xrpcerr.WithMessage("failed to encode response"),
132
-
), http.StatusInternalServerError)
133
-
return
134
-
}
126
+
writeJson(w, response)
135
127
}
136
128
137
129
// isTextualMimeType returns true if the MIME type represents textual content
+5
-16
knotserver/xrpc/repo_branch.go
+5
-16
knotserver/xrpc/repo_branch.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
5
"net/url"
6
+
"time"
7
7
8
8
"tangled.sh/tangled.sh/core/api/tangled"
9
9
"tangled.sh/tangled.sh/core/knotserver/git"
···
31
31
32
32
gr, err := git.PlainOpen(repoPath)
33
33
if err != nil {
34
-
writeError(w, xrpcerr.NewXrpcError(
35
-
xrpcerr.WithTag("RepoNotFound"),
36
-
xrpcerr.WithMessage("repository not found"),
37
-
), http.StatusNotFound)
34
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
38
35
return
39
36
}
40
37
···
70
67
Name: ref.Name().Short(),
71
68
Hash: ref.Hash().String(),
72
69
ShortHash: &[]string{ref.Hash().String()[:7]}[0],
73
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
70
+
When: commit.Author.When.Format(time.RFC3339),
74
71
IsDefault: &isDefault,
75
72
}
76
73
···
81
78
response.Author = &tangled.RepoBranch_Signature{
82
79
Name: commit.Author.Name,
83
80
Email: commit.Author.Email,
84
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
81
+
When: commit.Author.When.Format(time.RFC3339),
85
82
}
86
83
87
-
w.Header().Set("Content-Type", "application/json")
88
-
if err := json.NewEncoder(w).Encode(response); err != nil {
89
-
x.Logger.Error("failed to encode response", "error", err)
90
-
writeError(w, xrpcerr.NewXrpcError(
91
-
xrpcerr.WithTag("InternalServerError"),
92
-
xrpcerr.WithMessage("failed to encode response"),
93
-
), http.StatusInternalServerError)
94
-
return
95
-
}
84
+
writeJson(w, response)
96
85
}
+3
-19
knotserver/xrpc/repo_branches.go
+3
-19
knotserver/xrpc/repo_branches.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
5
"strconv"
7
6
···
31
30
32
31
gr, err := git.PlainOpen(repoPath)
33
32
if err != nil {
34
-
writeError(w, xrpcerr.NewXrpcError(
35
-
xrpcerr.WithTag("RepoNotFound"),
36
-
xrpcerr.WithMessage("repository not found"),
37
-
), http.StatusNotFound)
33
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
38
34
return
39
35
}
40
36
···
47
43
}
48
44
}
49
45
50
-
end := offset + limit
51
-
if end > len(branches) {
52
-
end = len(branches)
53
-
}
46
+
end := min(offset+limit, len(branches))
54
47
55
48
paginatedBranches := branches[offset:end]
56
49
···
59
52
Branches: paginatedBranches,
60
53
}
61
54
62
-
// Write JSON response directly
63
-
w.Header().Set("Content-Type", "application/json")
64
-
if err := json.NewEncoder(w).Encode(response); err != nil {
65
-
x.Logger.Error("failed to encode response", "error", err)
66
-
writeError(w, xrpcerr.NewXrpcError(
67
-
xrpcerr.WithTag("InternalServerError"),
68
-
xrpcerr.WithMessage("failed to encode response"),
69
-
), http.StatusInternalServerError)
70
-
return
71
-
}
55
+
writeJson(w, response)
72
56
}
+7
-23
knotserver/xrpc/repo_compare.go
+7
-23
knotserver/xrpc/repo_compare.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"fmt"
6
5
"net/http"
7
-
"net/url"
8
6
9
7
"tangled.sh/tangled.sh/core/knotserver/git"
10
8
"tangled.sh/tangled.sh/core/types"
···
19
17
return
20
18
}
21
19
22
-
rev1Param := r.URL.Query().Get("rev1")
23
-
if rev1Param == "" {
20
+
rev1 := r.URL.Query().Get("rev1")
21
+
if rev1 == "" {
24
22
writeError(w, xrpcerr.NewXrpcError(
25
23
xrpcerr.WithTag("InvalidRequest"),
26
24
xrpcerr.WithMessage("missing rev1 parameter"),
···
28
26
return
29
27
}
30
28
31
-
rev2Param := r.URL.Query().Get("rev2")
32
-
if rev2Param == "" {
29
+
rev2 := r.URL.Query().Get("rev2")
30
+
if rev2 == "" {
33
31
writeError(w, xrpcerr.NewXrpcError(
34
32
xrpcerr.WithTag("InvalidRequest"),
35
33
xrpcerr.WithMessage("missing rev2 parameter"),
···
37
35
return
38
36
}
39
37
40
-
rev1, _ := url.PathUnescape(rev1Param)
41
-
rev2, _ := url.PathUnescape(rev2Param)
42
-
43
38
gr, err := git.PlainOpen(repoPath)
44
39
if err != nil {
45
-
writeError(w, xrpcerr.NewXrpcError(
46
-
xrpcerr.WithTag("RepoNotFound"),
47
-
xrpcerr.WithMessage("repository not found"),
48
-
), http.StatusNotFound)
40
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
49
41
return
50
42
}
51
43
···
79
71
return
80
72
}
81
73
82
-
resp := types.RepoFormatPatchResponse{
74
+
response := types.RepoFormatPatchResponse{
83
75
Rev1: commit1.Hash.String(),
84
76
Rev2: commit2.Hash.String(),
85
77
FormatPatch: formatPatch,
86
78
Patch: rawPatch,
87
79
}
88
80
89
-
w.Header().Set("Content-Type", "application/json")
90
-
if err := json.NewEncoder(w).Encode(resp); err != nil {
91
-
x.Logger.Error("failed to encode response", "error", err)
92
-
writeError(w, xrpcerr.NewXrpcError(
93
-
xrpcerr.WithTag("InternalServerError"),
94
-
xrpcerr.WithMessage("failed to encode response"),
95
-
), http.StatusInternalServerError)
96
-
return
97
-
}
81
+
writeJson(w, response)
98
82
}
+6
-30
knotserver/xrpc/repo_diff.go
+6
-30
knotserver/xrpc/repo_diff.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
-
"net/url"
7
5
8
6
"tangled.sh/tangled.sh/core/knotserver/git"
9
7
"tangled.sh/tangled.sh/core/types"
···
18
16
return
19
17
}
20
18
21
-
refParam := r.URL.Query().Get("ref")
22
-
if refParam == "" {
23
-
writeError(w, xrpcerr.NewXrpcError(
24
-
xrpcerr.WithTag("InvalidRequest"),
25
-
xrpcerr.WithMessage("missing ref parameter"),
26
-
), http.StatusBadRequest)
27
-
return
28
-
}
29
-
30
-
ref, _ := url.QueryUnescape(refParam)
19
+
ref := r.URL.Query().Get("ref")
20
+
// ref can be empty (git.Open handles this)
31
21
32
22
gr, err := git.Open(repoPath, ref)
33
23
if err != nil {
34
-
writeError(w, xrpcerr.NewXrpcError(
35
-
xrpcerr.WithTag("RefNotFound"),
36
-
xrpcerr.WithMessage("repository or ref not found"),
37
-
), http.StatusNotFound)
24
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
38
25
return
39
26
}
40
27
41
28
diff, err := gr.Diff()
42
29
if err != nil {
43
30
x.Logger.Error("getting diff", "error", err.Error())
44
-
writeError(w, xrpcerr.NewXrpcError(
45
-
xrpcerr.WithTag("RefNotFound"),
46
-
xrpcerr.WithMessage("failed to generate diff"),
47
-
), http.StatusInternalServerError)
31
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusInternalServerError)
48
32
return
49
33
}
50
34
51
-
resp := types.RepoCommitResponse{
35
+
response := types.RepoCommitResponse{
52
36
Ref: ref,
53
37
Diff: diff,
54
38
}
55
39
56
-
w.Header().Set("Content-Type", "application/json")
57
-
if err := json.NewEncoder(w).Encode(resp); err != nil {
58
-
x.Logger.Error("failed to encode response", "error", err)
59
-
writeError(w, xrpcerr.NewXrpcError(
60
-
xrpcerr.WithTag("InternalServerError"),
61
-
xrpcerr.WithMessage("failed to encode response"),
62
-
), http.StatusInternalServerError)
63
-
return
64
-
}
40
+
writeJson(w, response)
65
41
}
+4
-19
knotserver/xrpc/repo_get_default_branch.go
+4
-19
knotserver/xrpc/repo_get_default_branch.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
5
+
"time"
6
6
7
7
"tangled.sh/tangled.sh/core/api/tangled"
8
8
"tangled.sh/tangled.sh/core/knotserver/git"
···
17
17
return
18
18
}
19
19
20
-
gr, err := git.Open(repoPath, "")
21
-
if err != nil {
22
-
writeError(w, xrpcerr.NewXrpcError(
23
-
xrpcerr.WithTag("RepoNotFound"),
24
-
xrpcerr.WithMessage("repository not found"),
25
-
), http.StatusNotFound)
26
-
return
27
-
}
20
+
gr, err := git.PlainOpen(repoPath)
28
21
29
22
branch, err := gr.FindMainBranch()
30
23
if err != nil {
···
39
32
response := tangled.RepoGetDefaultBranch_Output{
40
33
Name: branch,
41
34
Hash: "",
42
-
When: "1970-01-01T00:00:00.000Z",
35
+
When: time.UnixMicro(0).Format(time.RFC3339),
43
36
}
44
37
45
-
w.Header().Set("Content-Type", "application/json")
46
-
if err := json.NewEncoder(w).Encode(response); err != nil {
47
-
x.Logger.Error("failed to encode response", "error", err)
48
-
writeError(w, xrpcerr.NewXrpcError(
49
-
xrpcerr.WithTag("InternalServerError"),
50
-
xrpcerr.WithMessage("failed to encode response"),
51
-
), http.StatusInternalServerError)
52
-
return
53
-
}
38
+
writeJson(w, response)
54
39
}
+4
-21
knotserver/xrpc/repo_languages.go
+4
-21
knotserver/xrpc/repo_languages.go
···
2
2
3
3
import (
4
4
"context"
5
-
"encoding/json"
6
5
"math"
7
6
"net/http"
8
-
"net/url"
9
7
"time"
10
8
11
9
"tangled.sh/tangled.sh/core/api/tangled"
···
14
12
)
15
13
16
14
func (x *Xrpc) RepoLanguages(w http.ResponseWriter, r *http.Request) {
17
-
refParam := r.URL.Query().Get("ref")
18
-
if refParam == "" {
19
-
refParam = "HEAD" // default
20
-
}
21
-
ref, _ := url.PathUnescape(refParam)
22
-
23
15
repo := r.URL.Query().Get("repo")
24
16
repoPath, err := x.parseRepoParam(repo)
25
17
if err != nil {
···
27
19
return
28
20
}
29
21
22
+
ref := r.URL.Query().Get("ref")
23
+
30
24
gr, err := git.Open(repoPath, ref)
31
25
if err != nil {
32
26
x.Logger.Error("opening repo", "error", err.Error())
33
-
writeError(w, xrpcerr.NewXrpcError(
34
-
xrpcerr.WithTag("RefNotFound"),
35
-
xrpcerr.WithMessage("repository or ref not found"),
36
-
), http.StatusNotFound)
27
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
37
28
return
38
29
}
39
30
···
81
72
response.TotalFiles = &totalFiles
82
73
}
83
74
84
-
w.Header().Set("Content-Type", "application/json")
85
-
if err := json.NewEncoder(w).Encode(response); err != nil {
86
-
x.Logger.Error("failed to encode response", "error", err)
87
-
writeError(w, xrpcerr.NewXrpcError(
88
-
xrpcerr.WithTag("InternalServerError"),
89
-
xrpcerr.WithMessage("failed to encode response"),
90
-
), http.StatusInternalServerError)
91
-
return
92
-
}
75
+
writeJson(w, response)
93
76
}
+3
-33
knotserver/xrpc/repo_log.go
+3
-33
knotserver/xrpc/repo_log.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
-
"net/url"
7
5
"strconv"
8
6
9
7
"tangled.sh/tangled.sh/core/knotserver/git"
···
19
17
return
20
18
}
21
19
22
-
refParam := r.URL.Query().Get("ref")
23
-
if refParam == "" {
24
-
writeError(w, xrpcerr.NewXrpcError(
25
-
xrpcerr.WithTag("InvalidRequest"),
26
-
xrpcerr.WithMessage("missing ref parameter"),
27
-
), http.StatusBadRequest)
28
-
return
29
-
}
20
+
ref := r.URL.Query().Get("ref")
30
21
31
22
path := r.URL.Query().Get("path")
32
23
cursor := r.URL.Query().Get("cursor")
···
38
29
}
39
30
}
40
31
41
-
ref, err := url.QueryUnescape(refParam)
42
-
if err != nil {
43
-
writeError(w, xrpcerr.NewXrpcError(
44
-
xrpcerr.WithTag("InvalidRequest"),
45
-
xrpcerr.WithMessage("invalid ref parameter"),
46
-
), http.StatusBadRequest)
47
-
return
48
-
}
49
-
50
32
gr, err := git.Open(repoPath, ref)
51
33
if err != nil {
52
-
writeError(w, xrpcerr.NewXrpcError(
53
-
xrpcerr.WithTag("RefNotFound"),
54
-
xrpcerr.WithMessage("repository or ref not found"),
55
-
), http.StatusNotFound)
34
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
56
35
return
57
36
}
58
37
···
98
77
99
78
response.Log = true
100
79
101
-
// Write JSON response directly
102
-
w.Header().Set("Content-Type", "application/json")
103
-
if err := json.NewEncoder(w).Encode(response); err != nil {
104
-
x.Logger.Error("failed to encode response", "error", err)
105
-
writeError(w, xrpcerr.NewXrpcError(
106
-
xrpcerr.WithTag("InternalServerError"),
107
-
xrpcerr.WithMessage("failed to encode response"),
108
-
), http.StatusInternalServerError)
109
-
return
110
-
}
80
+
writeJson(w, response)
111
81
}
+6
-33
knotserver/xrpc/repo_tree.go
+6
-33
knotserver/xrpc/repo_tree.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"net/http"
6
-
"net/url"
7
5
"path/filepath"
6
+
"time"
8
7
9
8
"tangled.sh/tangled.sh/core/api/tangled"
10
9
"tangled.sh/tangled.sh/core/knotserver/git"
···
21
20
return
22
21
}
23
22
24
-
refParam := r.URL.Query().Get("ref")
25
-
if refParam == "" {
26
-
writeError(w, xrpcerr.NewXrpcError(
27
-
xrpcerr.WithTag("InvalidRequest"),
28
-
xrpcerr.WithMessage("missing ref parameter"),
29
-
), http.StatusBadRequest)
30
-
return
31
-
}
23
+
ref := r.URL.Query().Get("ref")
24
+
// ref can be empty (git.Open handles this)
32
25
33
26
path := r.URL.Query().Get("path")
34
27
// path can be empty (defaults to root)
35
28
36
-
ref, err := url.QueryUnescape(refParam)
37
-
if err != nil {
38
-
writeError(w, xrpcerr.NewXrpcError(
39
-
xrpcerr.WithTag("InvalidRequest"),
40
-
xrpcerr.WithMessage("invalid ref parameter"),
41
-
), http.StatusBadRequest)
42
-
return
43
-
}
44
-
45
29
gr, err := git.Open(repoPath, ref)
46
30
if err != nil {
47
31
x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref)
48
-
writeError(w, xrpcerr.NewXrpcError(
49
-
xrpcerr.WithTag("RefNotFound"),
50
-
xrpcerr.WithMessage("repository or ref not found"),
51
-
), http.StatusNotFound)
32
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
52
33
return
53
34
}
54
35
···
77
58
entry.Last_commit = &tangled.RepoTree_LastCommit{
78
59
Hash: file.LastCommit.Hash.String(),
79
60
Message: file.LastCommit.Message,
80
-
When: file.LastCommit.When.Format("2006-01-02T15:04:05.000Z"),
61
+
When: file.LastCommit.When.Format(time.RFC3339),
81
62
}
82
63
}
83
64
···
104
85
Files: treeEntries,
105
86
}
106
87
107
-
w.Header().Set("Content-Type", "application/json")
108
-
if err := json.NewEncoder(w).Encode(response); err != nil {
109
-
x.Logger.Error("failed to encode response", "error", err)
110
-
writeError(w, xrpcerr.NewXrpcError(
111
-
xrpcerr.WithTag("InternalServerError"),
112
-
xrpcerr.WithMessage("failed to encode response"),
113
-
), http.StatusInternalServerError)
114
-
return
115
-
}
88
+
writeJson(w, response)
116
89
}
+1
-11
knotserver/xrpc/version.go
+1
-11
knotserver/xrpc/version.go
···
1
1
package xrpc
2
2
3
3
import (
4
-
"encoding/json"
5
4
"fmt"
6
5
"net/http"
7
6
"runtime/debug"
8
7
9
8
"tangled.sh/tangled.sh/core/api/tangled"
10
-
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
11
9
)
12
10
13
11
// version is set during build time.
···
58
56
Version: version,
59
57
}
60
58
61
-
w.Header().Set("Content-Type", "application/json")
62
-
if err := json.NewEncoder(w).Encode(response); err != nil {
63
-
x.Logger.Error("failed to encode response", "error", err)
64
-
writeError(w, xrpcerr.NewXrpcError(
65
-
xrpcerr.WithTag("InternalServerError"),
66
-
xrpcerr.WithMessage("failed to encode response"),
67
-
), http.StatusInternalServerError)
68
-
return
69
-
}
59
+
writeJson(w, response)
70
60
}
+14
-35
knotserver/xrpc/xrpc.go
+14
-35
knotserver/xrpc/xrpc.go
···
4
4
"encoding/json"
5
5
"log/slog"
6
6
"net/http"
7
-
"net/url"
8
7
"strings"
9
8
10
9
securejoin "github.com/cyphar/filepath-securejoin"
···
88
87
}
89
88
90
89
// Parse repo string (did/repoName format)
91
-
parts := strings.Split(repo, "/")
92
-
if len(parts) < 2 {
90
+
parts := strings.SplitN(repo, "/", 2)
91
+
if len(parts) != 2 {
93
92
return "", xrpcerr.NewXrpcError(
94
93
xrpcerr.WithTag("InvalidRequest"),
95
94
xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"),
96
95
)
97
96
}
98
97
99
-
did := strings.Join(parts[:len(parts)-1], "/")
100
-
repoName := parts[len(parts)-1]
98
+
did := parts[0]
99
+
repoName := parts[1]
101
100
102
101
// Construct repository path using the same logic as didPath
103
102
didRepoPath, err := securejoin.SecureJoin(did, repoName)
104
103
if err != nil {
105
-
return "", xrpcerr.NewXrpcError(
106
-
xrpcerr.WithTag("RepoNotFound"),
107
-
xrpcerr.WithMessage("failed to access repository"),
108
-
)
104
+
return "", xrpcerr.RepoNotFoundError
109
105
}
110
106
111
107
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath)
112
108
if err != nil {
113
-
return "", xrpcerr.NewXrpcError(
114
-
xrpcerr.WithTag("RepoNotFound"),
115
-
xrpcerr.WithMessage("failed to access repository"),
116
-
)
109
+
return "", xrpcerr.RepoNotFoundError
117
110
}
118
111
119
112
return repoPath, nil
120
113
}
121
114
122
-
// parseStandardParams parses common query parameters used by most handlers
123
-
func (x *Xrpc) parseStandardParams(r *http.Request) (repo, repoPath, ref string, err error) {
124
-
// Parse repo parameter
125
-
repo = r.URL.Query().Get("repo")
126
-
repoPath, err = x.parseRepoParam(repo)
127
-
if err != nil {
128
-
return "", "", "", err
129
-
}
130
-
131
-
// Parse and unescape ref parameter
132
-
refParam := r.URL.Query().Get("ref")
133
-
if refParam == "" {
134
-
return "", "", "", xrpcerr.NewXrpcError(
135
-
xrpcerr.WithTag("InvalidRequest"),
136
-
xrpcerr.WithMessage("missing ref parameter"),
137
-
)
138
-
}
139
-
140
-
ref, _ = url.QueryUnescape(refParam)
141
-
return repo, repoPath, ref, nil
142
-
}
143
-
144
115
func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
145
116
w.Header().Set("Content-Type", "application/json")
146
117
w.WriteHeader(status)
147
118
json.NewEncoder(w).Encode(e)
148
119
}
120
+
121
+
func writeJson(w http.ResponseWriter, response any) {
122
+
w.Header().Set("Content-Type", "application/json")
123
+
if err := json.NewEncoder(w).Encode(response); err != nil {
124
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
125
+
return
126
+
}
127
+
}
+10
xrpc/errors/errors.go
+10
xrpc/errors/errors.go
···
56
56
WithMessage("owner not set for this service"),
57
57
)
58
58
59
+
var RepoNotFoundError = NewXrpcError(
60
+
WithTag("RepoNotFound"),
61
+
WithMessage("failed to access repository"),
62
+
)
63
+
64
+
var RefNotFoundError = NewXrpcError(
65
+
WithTag("RefNotFound"),
66
+
WithMessage("failed to access ref"),
67
+
)
68
+
59
69
var AuthError = func(err error) XrpcError {
60
70
return NewXrpcError(
61
71
WithTag("Auth"),