- {{ $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" }}
diff --git a/appview/repo/index.go b/appview/repo/index.go
--- a/appview/repo/index.go
+++ b/appview/repo/index.go
@@ -5,6 +5,7 @@ "errors"
"fmt"
"log"
"net/http"
+ "net/url"
"slices"
"sort"
"strings"
@@ -31,6 +32,7 @@ )
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 {
@@ -245,12 +247,12 @@
// 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
@@ -292,12 +294,12 @@ go func() {
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))
}
}()
@@ -307,7 +309,7 @@ go func() {
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
@@ -319,12 +321,12 @@ go func() {
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))
}
}()
diff --git a/appview/repo/repo.go b/appview/repo/repo.go
--- a/appview/repo/repo.go
+++ b/appview/repo/repo.go
@@ -85,7 +85,9 @@ }
}
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)
@@ -102,19 +104,16 @@ Host: host,
}
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)))
@@ -139,6 +138,7 @@ }
}
ref := chi.URLParam(r, "ref")
+ ref, _ = url.PathUnescape(ref)
scheme := "http"
if !rp.config.Core.Dev {
@@ -159,13 +159,9 @@ }
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
}
@@ -177,12 +173,10 @@ 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)
@@ -196,12 +190,10 @@ }
}
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 {
@@ -353,6 +345,7 @@ log.Println("failed to fully resolve repo", err)
return
}
ref := chi.URLParam(r, "ref")
+ ref, _ = url.PathUnescape(ref)
var diffOpts types.DiffOpts
if d := r.URL.Query().Get("diff"); d == "split" {
@@ -375,13 +368,9 @@ }
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
}
@@ -433,10 +422,12 @@ 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"
@@ -450,13 +441,9 @@ }
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
}
@@ -498,19 +485,19 @@ }
// 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))})
}
}
@@ -543,13 +530,9 @@ }
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
}
@@ -616,13 +599,9 @@ }
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
}
@@ -651,7 +630,10 @@ 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 {
@@ -664,23 +646,19 @@ }
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))})
}
}
@@ -710,8 +688,19 @@ }
// 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 {
@@ -766,7 +755,10 @@ 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 {
@@ -774,8 +766,18 @@ scheme = "https"
}
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 {
@@ -1364,12 +1366,8 @@ }
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
}
@@ -1471,6 +1469,7 @@ }
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)
@@ -1759,13 +1758,9 @@ }
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
}
@@ -1800,13 +1795,9 @@ head = queryHead
}
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
}
@@ -1877,13 +1868,9 @@
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
}
@@ -1895,13 +1882,9 @@ 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
}
@@ -1913,13 +1896,9 @@ 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
}
diff --git a/knotserver/xrpc/repo_archive.go b/knotserver/xrpc/repo_archive.go
--- a/knotserver/xrpc/repo_archive.go
+++ b/knotserver/xrpc/repo_archive.go
@@ -13,12 +13,16 @@ xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
)
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 == "" {
format = "tar.gz" // default
@@ -34,7 +38,7 @@ ), http.StatusBadRequest)
return
}
- gr, err := git.Open(repoPath, unescapedRef)
+ gr, err := git.Open(repoPath, ref)
if err != nil {
writeError(w, xrpcerr.NewXrpcError(
xrpcerr.WithTag("RefNotFound"),
@@ -46,7 +50,7 @@
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 != "" {
diff --git a/knotserver/xrpc/repo_blob.go b/knotserver/xrpc/repo_blob.go
--- a/knotserver/xrpc/repo_blob.go
+++ b/knotserver/xrpc/repo_blob.go
@@ -16,11 +16,15 @@ xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
)
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 == "" {
diff --git a/knotserver/xrpc/repo_branch.go b/knotserver/xrpc/repo_branch.go
--- a/knotserver/xrpc/repo_branch.go
+++ b/knotserver/xrpc/repo_branch.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"net/url"
+ "time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
@@ -70,7 +71,7 @@ response := tangled.RepoBranch_Output{
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,
}
@@ -81,7 +82,7 @@
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")
diff --git a/knotserver/xrpc/repo_branches.go b/knotserver/xrpc/repo_branches.go
--- a/knotserver/xrpc/repo_branches.go
+++ b/knotserver/xrpc/repo_branches.go
@@ -47,10 +47,7 @@ offset = o
}
}
- end := offset + limit
- if end > len(branches) {
- end = len(branches)
- }
+ end := min(offset+limit, len(branches))
paginatedBranches := branches[offset:end]
diff --git a/knotserver/xrpc/repo_compare.go b/knotserver/xrpc/repo_compare.go
--- a/knotserver/xrpc/repo_compare.go
+++ b/knotserver/xrpc/repo_compare.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/url"
"tangled.sh/tangled.sh/core/knotserver/git"
"tangled.sh/tangled.sh/core/types"
@@ -19,8 +18,8 @@ writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
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"),
@@ -28,17 +27,14 @@ ), http.StatusBadRequest)
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"),
), http.StatusBadRequest)
return
}
-
- rev1, _ := url.PathUnescape(rev1Param)
- rev2, _ := url.PathUnescape(rev2Param)
gr, err := git.PlainOpen(repoPath)
if err != nil {
diff --git a/knotserver/xrpc/repo_diff.go b/knotserver/xrpc/repo_diff.go
--- a/knotserver/xrpc/repo_diff.go
+++ b/knotserver/xrpc/repo_diff.go
@@ -3,7 +3,6 @@
import (
"encoding/json"
"net/http"
- "net/url"
"tangled.sh/tangled.sh/core/knotserver/git"
"tangled.sh/tangled.sh/core/types"
@@ -18,16 +17,8 @@ writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
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 {
diff --git a/knotserver/xrpc/repo_get_default_branch.go b/knotserver/xrpc/repo_get_default_branch.go
--- a/knotserver/xrpc/repo_get_default_branch.go
+++ b/knotserver/xrpc/repo_get_default_branch.go
@@ -3,6 +3,7 @@
import (
"encoding/json"
"net/http"
+ "time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
@@ -17,14 +18,7 @@ writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
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 {
@@ -39,7 +33,7 @@
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")
diff --git a/knotserver/xrpc/repo_languages.go b/knotserver/xrpc/repo_languages.go
--- a/knotserver/xrpc/repo_languages.go
+++ b/knotserver/xrpc/repo_languages.go
@@ -5,7 +5,6 @@ "context"
"encoding/json"
"math"
"net/http"
- "net/url"
"time"
"tangled.sh/tangled.sh/core/api/tangled"
@@ -14,18 +13,14 @@ xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
)
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 {
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
return
}
+
+ ref := r.URL.Query().Get("ref")
gr, err := git.Open(repoPath, ref)
if err != nil {
diff --git a/knotserver/xrpc/repo_log.go b/knotserver/xrpc/repo_log.go
--- a/knotserver/xrpc/repo_log.go
+++ b/knotserver/xrpc/repo_log.go
@@ -3,7 +3,6 @@
import (
"encoding/json"
"net/http"
- "net/url"
"strconv"
"tangled.sh/tangled.sh/core/knotserver/git"
@@ -19,14 +18,7 @@ writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
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")
@@ -36,15 +28,6 @@ if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
limit = l
}
- }
-
- 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)
diff --git a/knotserver/xrpc/repo_tags.go b/knotserver/xrpc/repo_tags.go
--- a/knotserver/xrpc/repo_tags.go
+++ b/knotserver/xrpc/repo_tags.go
@@ -30,13 +30,13 @@ limit = l
}
}
- 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)
+ ), http.StatusNoContent)
return
}
diff --git a/knotserver/xrpc/repo_tree.go b/knotserver/xrpc/repo_tree.go
--- a/knotserver/xrpc/repo_tree.go
+++ b/knotserver/xrpc/repo_tree.go
@@ -3,8 +3,8 @@
import (
"encoding/json"
"net/http"
- "net/url"
"path/filepath"
+ "time"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
@@ -21,27 +21,12 @@ writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
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)
@@ -77,7 +62,7 @@ if file.LastCommit != nil {
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),
}
}
diff --git a/knotserver/xrpc/xrpc.go b/knotserver/xrpc/xrpc.go
--- a/knotserver/xrpc/xrpc.go
+++ b/knotserver/xrpc/xrpc.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"log/slog"
"net/http"
- "net/url"
"strings"
securejoin "github.com/cyphar/filepath-securejoin"
@@ -88,16 +87,16 @@ )
}
// 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)
@@ -117,28 +116,6 @@ )
}
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) {