forked from tangled.org/core
this repo has no description

Compare changes

Choose any two refs to compare.

+4 -8
appview/db/issues.go
···
repoMap[string(repos[i].RepoAt())] = &repos[i]
}
-
for issueAt, i := range issueMap {
-
if r, ok := repoMap[string(i.RepoAt)]; ok {
-
i.Repo = r
-
} else {
-
// do not show up the issue if the repo is deleted
-
// TODO: foreign key where?
-
delete(issueMap, issueAt)
-
}
+
for issueAt := range issueMap {
+
i := issueMap[issueAt]
+
r := repoMap[string(i.RepoAt)]
+
i.Repo = r
}
// collect comments
+3 -3
appview/pages/templates/repo/tree.html
···
<div class="flex flex-col md:flex-row md:justify-between gap-2">
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
{{ range .BreadCrumbs }}
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
+
<a href="{{ index . 1 }}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
{{ end }}
</div>
<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">
{{ $stats := .TreeStats }}
-
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}">{{ $.Ref }}</a></span>
+
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ pathEscape $.Ref }}">{{ $.Ref }}</a></span>
{{ if eq $stats.NumFolders 1 }}
<span class="select-none px-1 md:px-2 [&:before]:content-['ยท']"></span>
<span>{{ $stats.NumFolders }} folder</span>
···
{{ range .Files }}
<div class="grid grid-cols-12 gap-4 items-center py-1">
<div class="col-span-8 md:col-span-4">
-
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (urlquery $.Ref) $.TreePath .Name }}
+
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (pathEscape $.Ref) $.TreePath .Name }}
{{ $icon := "folder" }}
{{ $iconStyle := "size-4 fill-current" }}
+16 -17
appview/repo/index.go
···
"fmt"
"log"
"net/http"
+
"net/url"
"slices"
"sort"
"strings"
···
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
f, err := rp.repoResolver.Resolve(r)
if err != nil {
···
RepoInfo: repoInfo,
})
return
-
} else {
-
rp.pages.Error503(w)
-
log.Println("failed to build index response", err)
-
return
}
+
+
rp.pages.Error503(w)
+
log.Println("failed to build index response", err)
+
return
}
tagMap := make(map[string][]string)
···
// first get branches to determine the ref if not specified
branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repo)
if err != nil {
-
return nil, err
+
return nil, fmt.Errorf("failed to call repoBranches: %w", err)
}
var branchesResp types.RepoBranchesResponse
if err := json.Unmarshal(branchesBytes, &branchesResp); err != nil {
-
return nil, err
+
return nil, fmt.Errorf("failed to unmarshal branches response: %w", err)
}
// if no ref specified, use default branch or first available
-
if ref == "" && len(branchesResp.Branches) > 0 {
+
if ref == "" {
for _, branch := range branchesResp.Branches {
if branch.IsDefault {
ref = branch.Name
break
}
}
-
if ref == "" {
-
ref = branchesResp.Branches[0].Name
-
}
}
-
// check if repo is empty
-
if len(branchesResp.Branches) == 0 {
+
// if ref is still empty, this means the default branch is not set
+
if ref == "" {
return &types.RepoIndexResponse{
IsEmpty: true,
Branches: branchesResp.Branches,
···
defer wg.Done()
tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo)
if err != nil {
-
errs = errors.Join(errs, err)
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTags: %w", err))
return
}
if err := json.Unmarshal(tagsBytes, &tagsResp); err != nil {
-
errs = errors.Join(errs, err)
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoTags: %w", err))
}
}()
···
defer wg.Done()
resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repo)
if err != nil {
-
errs = errors.Join(errs, err)
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTree: %w", err))
return
}
treeResp = resp
···
defer wg.Done()
logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repo)
if err != nil {
-
errs = errors.Join(errs, err)
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoLog: %w", err))
return
}
if err := json.Unmarshal(logBytes, &logResp); err != nil {
-
errs = errors.Join(errs, err)
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoLog: %w", err))
}
}()
+97 -118
appview/repo/repo.go
···
}
func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
-
refParam := chi.URLParam(r, "ref")
+
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
+
f, err := rp.repoResolver.Resolve(r)
if err != nil {
log.Println("failed to get repo and knot", err)
···
}
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
-
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", refParam, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.archive", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.archive", xrpcerr)
+
rp.pages.Error503(w)
return
}
-
// Set headers for file download
-
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, refParam)
+
// Set headers for file download, just pass along whatever the knot specifies
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
+
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
w.Header().Set("Content-Type", "application/gzip")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
···
}
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
scheme := "http"
if !rp.config.Core.Dev {
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.log", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.log", xrpcerr)
+
rp.pages.Error503(w)
return
}
···
}
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
+
rp.pages.Error503(w)
+
return
}
tagMap := make(map[string][]string)
···
}
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
+
rp.pages.Error503(w)
+
return
}
if branchBytes != nil {
···
return
}
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
var diffOpts types.DiffOpts
if d := r.URL.Query().Get("diff"); d == "split" {
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.diff", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.diff", xrpcerr)
+
rp.pages.Error503(w)
return
}
···
}
ref := chi.URLParam(r, "ref")
-
treePath := chi.URLParam(r, "*")
+
ref, _ = url.PathUnescape(ref)
// if the tree path has a trailing slash, let's strip it
// so we don't 404
+
treePath := chi.URLParam(r, "*")
+
treePath, _ = url.PathUnescape(treePath)
treePath = strings.TrimSuffix(treePath, "/")
scheme := "http"
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.tree", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.tree", xrpcerr)
+
rp.pages.Error503(w)
return
}
···
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
// so we can safely redirect to the "parent" (which is the same file).
-
unescapedTreePath, _ := url.PathUnescape(treePath)
-
if len(result.Files) == 0 && result.Parent == unescapedTreePath {
-
http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)
+
if len(result.Files) == 0 && result.Parent == treePath {
+
redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent)
+
http.Redirect(w, r, redirectTo, http.StatusFound)
return
}
user := rp.oauth.GetUser(r)
var breadcrumbs [][]string
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
if treePath != "" {
for idx, elem := range strings.Split(treePath, "/") {
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
}
}
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
+
rp.pages.Error503(w)
return
}
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
+
rp.pages.Error503(w)
return
}
···
}
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
+
filePath := chi.URLParam(r, "*")
+
filePath, _ = url.PathUnescape(filePath)
scheme := "http"
if !rp.config.Core.Dev {
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.blob", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Error404(w)
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.blob", xrpcerr)
+
rp.pages.Error503(w)
return
}
// Use XRPC response directly instead of converting to internal types
var breadcrumbs [][]string
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
if filePath != "" {
for idx, elem := range strings.Split(filePath, "/") {
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
}
}
···
// fetch the raw binary content using sh.tangled.repo.blob xrpc
repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
-
scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath))
+
+
baseURL := &url.URL{
+
Scheme: scheme,
+
Host: f.Knot,
+
Path: "/xrpc/sh.tangled.repo.blob",
+
}
+
query := baseURL.Query()
+
query.Set("repo", repoName)
+
query.Set("ref", ref)
+
query.Set("path", filePath)
+
query.Set("raw", "true")
+
baseURL.RawQuery = query.Encode()
+
blobURL := baseURL.String()
contentSrc = blobURL
if !rp.config.Core.Dev {
···
}
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
+
filePath := chi.URLParam(r, "*")
+
filePath, _ = url.PathUnescape(filePath)
scheme := "http"
if !rp.config.Core.Dev {
···
}
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
-
scheme, f.Knot, url.QueryEscape(repo), url.QueryEscape(ref), url.QueryEscape(filePath))
+
baseURL := &url.URL{
+
Scheme: scheme,
+
Host: f.Knot,
+
Path: "/xrpc/sh.tangled.repo.blob",
+
}
+
query := baseURL.Query()
+
query.Set("repo", repo)
+
query.Set("ref", ref)
+
query.Set("path", filePath)
+
query.Set("raw", "true")
+
baseURL.RawQuery = query.Encode()
+
blobURL := baseURL.String()
req, err := http.NewRequest("GET", blobURL, nil)
if err != nil {
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
rp.pages.Error503(w)
return
···
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
user := rp.oauth.GetUser(r)
f, err := rp.repoResolver.Resolve(r)
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
+
rp.pages.Error503(w)
return
···
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
+
rp.pages.Error503(w)
return
···
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
+
rp.pages.Error503(w)
return
···
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
+
rp.pages.Error503(w)
return
···
compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
-
if err != nil {
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
log.Println("failed to call XRPC repo.compare", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
log.Println("failed to call XRPC repo.compare", xrpcerr)
+
rp.pages.Error503(w)
return
+1 -10
knotserver/xrpc/list_keys.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
"strconv"
···
response.Cursor = &nextCursor
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+1 -10
knotserver/xrpc/owner.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
"tangled.sh/tangled.sh/core/api/tangled"
···
Owner: owner,
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+8 -7
knotserver/xrpc/repo_archive.go
···
)
func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) {
-
repo, repoPath, unescapedRef, err := x.parseStandardParams(r)
+
repo := r.URL.Query().Get("repo")
+
repoPath, err := x.parseRepoParam(repo)
if err != nil {
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
return
}
+
+
ref := r.URL.Query().Get("ref")
+
// ref can be empty (git.Open handles this)
format := r.URL.Query().Get("format")
if format == "" {
···
return
}
-
gr, err := git.Open(repoPath, unescapedRef)
+
gr, err := git.Open(repoPath, ref)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
repoParts := strings.Split(repo, "/")
repoName := repoParts[len(repoParts)-1]
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
var archivePrefix string
if prefix != "" {
+7 -15
knotserver/xrpc/repo_blob.go
···
import (
"crypto/sha256"
"encoding/base64"
-
"encoding/json"
"fmt"
"net/http"
"path/filepath"
···
)
func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) {
-
_, repoPath, ref, err := x.parseStandardParams(r)
+
repo := r.URL.Query().Get("repo")
+
repoPath, err := x.parseRepoParam(repo)
if err != nil {
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
return
}
+
ref := r.URL.Query().Get("ref")
+
// ref can be empty (git.Open handles this)
+
treePath := r.URL.Query().Get("path")
if treePath == "" {
writeError(w, xrpcerr.NewXrpcError(
···
gr, err := git.Open(repoPath, ref)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
···
response.MimeType = &mimeType
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
// isTextualMimeType returns true if the MIME type represents textual content
+5 -16
knotserver/xrpc/repo_branch.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
"net/url"
+
"time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
···
gr, err := git.PlainOpen(repoPath)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("repository not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
return
}
···
Name: ref.Name().Short(),
Hash: ref.Hash().String(),
ShortHash: &[]string{ref.Hash().String()[:7]}[0],
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
+
When: commit.Author.When.Format(time.RFC3339),
IsDefault: &isDefault,
}
···
response.Author = &tangled.RepoBranch_Signature{
Name: commit.Author.Name,
Email: commit.Author.Email,
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
+
When: commit.Author.When.Format(time.RFC3339),
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+3 -19
knotserver/xrpc/repo_branches.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
"strconv"
···
gr, err := git.PlainOpen(repoPath)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("repository not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
return
}
···
}
}
-
end := offset + limit
-
if end > len(branches) {
-
end = len(branches)
-
}
+
end := min(offset+limit, len(branches))
paginatedBranches := branches[offset:end]
···
Branches: paginatedBranches,
}
-
// Write JSON response directly
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+7 -23
knotserver/xrpc/repo_compare.go
···
package xrpc
import (
-
"encoding/json"
"fmt"
"net/http"
-
"net/url"
"tangled.sh/tangled.sh/core/knotserver/git"
"tangled.sh/tangled.sh/core/types"
···
return
}
-
rev1Param := r.URL.Query().Get("rev1")
-
if rev1Param == "" {
+
rev1 := r.URL.Query().Get("rev1")
+
if rev1 == "" {
writeError(w, xrpcerr.NewXrpcError(
xrpcerr.WithTag("InvalidRequest"),
xrpcerr.WithMessage("missing rev1 parameter"),
···
return
}
-
rev2Param := r.URL.Query().Get("rev2")
-
if rev2Param == "" {
+
rev2 := r.URL.Query().Get("rev2")
+
if rev2 == "" {
writeError(w, xrpcerr.NewXrpcError(
xrpcerr.WithTag("InvalidRequest"),
xrpcerr.WithMessage("missing rev2 parameter"),
···
return
}
-
rev1, _ := url.PathUnescape(rev1Param)
-
rev2, _ := url.PathUnescape(rev2Param)
-
gr, err := git.PlainOpen(repoPath)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("repository not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
return
}
···
return
}
-
resp := types.RepoFormatPatchResponse{
+
response := types.RepoFormatPatchResponse{
Rev1: commit1.Hash.String(),
Rev2: commit2.Hash.String(),
FormatPatch: formatPatch,
Patch: rawPatch,
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(resp); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+6 -30
knotserver/xrpc/repo_diff.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
-
"net/url"
"tangled.sh/tangled.sh/core/knotserver/git"
"tangled.sh/tangled.sh/core/types"
···
return
}
-
refParam := r.URL.Query().Get("ref")
-
if refParam == "" {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("missing ref parameter"),
-
), http.StatusBadRequest)
-
return
-
}
-
-
ref, _ := url.QueryUnescape(refParam)
+
ref := r.URL.Query().Get("ref")
+
// ref can be empty (git.Open handles this)
gr, err := git.Open(repoPath, ref)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
diff, err := gr.Diff()
if err != nil {
x.Logger.Error("getting diff", "error", err.Error())
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("failed to generate diff"),
-
), http.StatusInternalServerError)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusInternalServerError)
return
}
-
resp := types.RepoCommitResponse{
+
response := types.RepoCommitResponse{
Ref: ref,
Diff: diff,
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(resp); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+4 -19
knotserver/xrpc/repo_get_default_branch.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
+
"time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
···
return
}
-
gr, err := git.Open(repoPath, "")
-
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("repository not found"),
-
), http.StatusNotFound)
-
return
-
}
+
gr, err := git.PlainOpen(repoPath)
branch, err := gr.FindMainBranch()
if err != nil {
···
response := tangled.RepoGetDefaultBranch_Output{
Name: branch,
Hash: "",
-
When: "1970-01-01T00:00:00.000Z",
+
When: time.UnixMicro(0).Format(time.RFC3339),
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+4 -21
knotserver/xrpc/repo_languages.go
···
import (
"context"
-
"encoding/json"
"math"
"net/http"
-
"net/url"
"time"
"tangled.sh/tangled.sh/core/api/tangled"
···
)
func (x *Xrpc) RepoLanguages(w http.ResponseWriter, r *http.Request) {
-
refParam := r.URL.Query().Get("ref")
-
if refParam == "" {
-
refParam = "HEAD" // default
-
}
-
ref, _ := url.PathUnescape(refParam)
-
repo := r.URL.Query().Get("repo")
repoPath, err := x.parseRepoParam(repo)
if err != nil {
···
return
}
+
ref := r.URL.Query().Get("ref")
+
gr, err := git.Open(repoPath, ref)
if err != nil {
x.Logger.Error("opening repo", "error", err.Error())
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
···
response.TotalFiles = &totalFiles
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+3 -33
knotserver/xrpc/repo_log.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
-
"net/url"
"strconv"
"tangled.sh/tangled.sh/core/knotserver/git"
···
return
}
-
refParam := r.URL.Query().Get("ref")
-
if refParam == "" {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("missing ref parameter"),
-
), http.StatusBadRequest)
-
return
-
}
+
ref := r.URL.Query().Get("ref")
path := r.URL.Query().Get("path")
cursor := r.URL.Query().Get("cursor")
···
}
}
-
ref, err := url.QueryUnescape(refParam)
-
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("invalid ref parameter"),
-
), http.StatusBadRequest)
-
return
-
}
-
gr, err := git.Open(repoPath, ref)
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
···
response.Log = true
-
// Write JSON response directly
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+3 -16
knotserver/xrpc/repo_tags.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
"strconv"
···
}
}
-
gr, err := git.Open(repoPath, "")
+
gr, err := git.PlainOpen(repoPath)
if err != nil {
x.Logger.Error("failed to open", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("repository not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
return
}
···
Tags: paginatedTags,
}
-
// Write JSON response directly
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+6 -33
knotserver/xrpc/repo_tree.go
···
package xrpc
import (
-
"encoding/json"
"net/http"
-
"net/url"
"path/filepath"
+
"time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
···
return
}
-
refParam := r.URL.Query().Get("ref")
-
if refParam == "" {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("missing ref parameter"),
-
), http.StatusBadRequest)
-
return
-
}
+
ref := r.URL.Query().Get("ref")
+
// ref can be empty (git.Open handles this)
path := r.URL.Query().Get("path")
// path can be empty (defaults to root)
-
ref, err := url.QueryUnescape(refParam)
-
if err != nil {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("invalid ref parameter"),
-
), http.StatusBadRequest)
-
return
-
}
-
gr, err := git.Open(repoPath, ref)
if err != nil {
x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RefNotFound"),
-
xrpcerr.WithMessage("repository or ref not found"),
-
), http.StatusNotFound)
+
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
return
}
···
entry.Last_commit = &tangled.RepoTree_LastCommit{
Hash: file.LastCommit.Hash.String(),
Message: file.LastCommit.Message,
-
When: file.LastCommit.When.Format("2006-01-02T15:04:05.000Z"),
+
When: file.LastCommit.When.Format(time.RFC3339),
}
}
···
Files: treeEntries,
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+1 -11
knotserver/xrpc/version.go
···
package xrpc
import (
-
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"tangled.sh/tangled.sh/core/api/tangled"
-
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
)
// version is set during build time.
···
Version: version,
}
-
w.Header().Set("Content-Type", "application/json")
-
if err := json.NewEncoder(w).Encode(response); err != nil {
-
x.Logger.Error("failed to encode response", "error", err)
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InternalServerError"),
-
xrpcerr.WithMessage("failed to encode response"),
-
), http.StatusInternalServerError)
-
return
-
}
+
writeJson(w, response)
}
+14 -35
knotserver/xrpc/xrpc.go
···
"encoding/json"
"log/slog"
"net/http"
-
"net/url"
"strings"
securejoin "github.com/cyphar/filepath-securejoin"
···
}
// Parse repo string (did/repoName format)
-
parts := strings.Split(repo, "/")
-
if len(parts) < 2 {
+
parts := strings.SplitN(repo, "/", 2)
+
if len(parts) != 2 {
return "", xrpcerr.NewXrpcError(
xrpcerr.WithTag("InvalidRequest"),
xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"),
)
}
-
did := strings.Join(parts[:len(parts)-1], "/")
-
repoName := parts[len(parts)-1]
+
did := parts[0]
+
repoName := parts[1]
// Construct repository path using the same logic as didPath
didRepoPath, err := securejoin.SecureJoin(did, repoName)
if err != nil {
-
return "", xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("failed to access repository"),
-
)
+
return "", xrpcerr.RepoNotFoundError
}
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath)
if err != nil {
-
return "", xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("RepoNotFound"),
-
xrpcerr.WithMessage("failed to access repository"),
-
)
+
return "", xrpcerr.RepoNotFoundError
}
return repoPath, nil
}
-
// parseStandardParams parses common query parameters used by most handlers
-
func (x *Xrpc) parseStandardParams(r *http.Request) (repo, repoPath, ref string, err error) {
-
// Parse repo parameter
-
repo = r.URL.Query().Get("repo")
-
repoPath, err = x.parseRepoParam(repo)
-
if err != nil {
-
return "", "", "", err
-
}
-
-
// Parse and unescape ref parameter
-
refParam := r.URL.Query().Get("ref")
-
if refParam == "" {
-
return "", "", "", xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("missing ref parameter"),
-
)
-
}
-
-
ref, _ = url.QueryUnescape(refParam)
-
return repo, repoPath, ref, nil
-
}
-
func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(e)
}
+
+
func writeJson(w http.ResponseWriter, response any) {
+
w.Header().Set("Content-Type", "application/json")
+
if err := json.NewEncoder(w).Encode(response); err != nil {
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
+
return
+
}
+
}
+10
xrpc/errors/errors.go
···
WithMessage("owner not set for this service"),
)
+
var RepoNotFoundError = NewXrpcError(
+
WithTag("RepoNotFound"),
+
WithMessage("failed to access repository"),
+
)
+
+
var RefNotFoundError = NewXrpcError(
+
WithTag("RefNotFound"),
+
WithMessage("failed to access ref"),
+
)
+
var AuthError = func(err error) XrpcError {
return NewXrpcError(
WithTag("Auth"),