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

knotserver: clean up routes and delete old handlers

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

Changed files
+32 -1175
knotserver
+2 -2
knotserver/events.go
···
WriteBufferSize: 1024,
}
-
func (h *Handle) Events(w http.ResponseWriter, r *http.Request) {
+
func (h *Knot) Events(w http.ResponseWriter, r *http.Request) {
l := h.l.With("handler", "OpLog")
l.Debug("received new connection")
···
}
}
-
func (h *Handle) streamOps(conn *websocket.Conn, cursor *int64) error {
+
func (h *Knot) streamOps(conn *websocket.Conn, cursor *int64) error {
events, err := h.db.GetEvents(*cursor)
if err != nil {
h.l.Error("failed to fetch events from db", "err", err, "cursor", cursor)
-48
knotserver/file.go
···
-
package knotserver
-
-
import (
-
"bytes"
-
"io"
-
"log/slog"
-
"net/http"
-
"strings"
-
-
"tangled.sh/tangled.sh/core/types"
-
)
-
-
func countLines(r io.Reader) (int, error) {
-
buf := make([]byte, 32*1024)
-
bufLen := 0
-
count := 0
-
nl := []byte{'\n'}
-
-
for {
-
c, err := r.Read(buf)
-
if c > 0 {
-
bufLen += c
-
}
-
count += bytes.Count(buf[:c], nl)
-
-
switch {
-
case err == io.EOF:
-
/* handle last line not having a newline at the end */
-
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
-
count++
-
}
-
return count, nil
-
case err != nil:
-
return 0, err
-
}
-
}
-
}
-
-
func (h *Handle) showFile(resp types.RepoBlobResponse, w http.ResponseWriter, l *slog.Logger) {
-
lc, err := countLines(strings.NewReader(resp.Contents))
-
if err != nil {
-
// Non-fatal, we'll just skip showing line numbers in the template.
-
l.Warn("counting lines", "error", err)
-
}
-
-
resp.Lines = lc
-
writeJSON(w, resp)
-
}
+4 -4
knotserver/git.go
···
"tangled.sh/tangled.sh/core/knotserver/git/service"
)
-
func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) {
+
func (d *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) {
did := chi.URLParam(r, "did")
name := chi.URLParam(r, "name")
repoName, err := securejoin.SecureJoin(did, name)
···
}
}
-
func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
+
func (d *Knot) UploadPack(w http.ResponseWriter, r *http.Request) {
did := chi.URLParam(r, "did")
name := chi.URLParam(r, "name")
repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
···
}
}
-
func (d *Handle) ReceivePack(w http.ResponseWriter, r *http.Request) {
+
func (d *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) {
did := chi.URLParam(r, "did")
name := chi.URLParam(r, "name")
_, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
···
d.RejectPush(w, r, name)
}
-
func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
+
func (d *Knot) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
// A text/plain response will cause git to print each line of the body
// prefixed with "remote: ".
w.Header().Set("content-type", "text/plain; charset=UTF-8")
-1069
knotserver/handler.go
···
-
package knotserver
-
-
import (
-
"compress/gzip"
-
"context"
-
"crypto/sha256"
-
"encoding/json"
-
"errors"
-
"fmt"
-
"log"
-
"net/http"
-
"net/url"
-
"path/filepath"
-
"strconv"
-
"strings"
-
"sync"
-
"time"
-
-
securejoin "github.com/cyphar/filepath-securejoin"
-
"github.com/gliderlabs/ssh"
-
"github.com/go-chi/chi/v5"
-
"github.com/go-git/go-git/v5/plumbing"
-
"github.com/go-git/go-git/v5/plumbing/object"
-
"tangled.sh/tangled.sh/core/knotserver/db"
-
"tangled.sh/tangled.sh/core/knotserver/git"
-
"tangled.sh/tangled.sh/core/types"
-
)
-
-
func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
-
w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
-
}
-
-
func (h *Handle) Capabilities(w http.ResponseWriter, r *http.Request) {
-
w.Header().Set("Content-Type", "application/json")
-
-
capabilities := map[string]any{
-
"pull_requests": map[string]any{
-
"format_patch": true,
-
"patch_submissions": true,
-
"branch_submissions": true,
-
"fork_submissions": true,
-
},
-
"xrpc": true,
-
}
-
-
jsonData, err := json.Marshal(capabilities)
-
if err != nil {
-
http.Error(w, "Failed to serialize JSON", http.StatusInternalServerError)
-
return
-
}
-
-
w.Write(jsonData)
-
}
-
-
func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
l := h.l.With("path", path, "handler", "RepoIndex")
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
plain, err2 := git.PlainOpen(path)
-
if err2 != nil {
-
l.Error("opening repo", "error", err2.Error())
-
notFound(w)
-
return
-
}
-
branches, _ := plain.Branches()
-
-
log.Println(err)
-
-
if errors.Is(err, plumbing.ErrReferenceNotFound) {
-
resp := types.RepoIndexResponse{
-
IsEmpty: true,
-
Branches: branches,
-
}
-
writeJSON(w, resp)
-
return
-
} else {
-
l.Error("opening repo", "error", err.Error())
-
notFound(w)
-
return
-
}
-
}
-
-
var (
-
commits []*object.Commit
-
total int
-
branches []types.Branch
-
files []types.NiceTree
-
tags []object.Tag
-
)
-
-
var wg sync.WaitGroup
-
errorsCh := make(chan error, 5)
-
-
wg.Add(1)
-
go func() {
-
defer wg.Done()
-
cs, err := gr.Commits(0, 60)
-
if err != nil {
-
errorsCh <- fmt.Errorf("commits: %w", err)
-
return
-
}
-
commits = cs
-
}()
-
-
wg.Add(1)
-
go func() {
-
defer wg.Done()
-
t, err := gr.TotalCommits()
-
if err != nil {
-
errorsCh <- fmt.Errorf("calculating total: %w", err)
-
return
-
}
-
total = t
-
}()
-
-
wg.Add(1)
-
go func() {
-
defer wg.Done()
-
bs, err := gr.Branches()
-
if err != nil {
-
errorsCh <- fmt.Errorf("fetching branches: %w", err)
-
return
-
}
-
branches = bs
-
}()
-
-
wg.Add(1)
-
go func() {
-
defer wg.Done()
-
ts, err := gr.Tags()
-
if err != nil {
-
errorsCh <- fmt.Errorf("fetching tags: %w", err)
-
return
-
}
-
tags = ts
-
}()
-
-
wg.Add(1)
-
go func() {
-
defer wg.Done()
-
fs, err := gr.FileTree(r.Context(), "")
-
if err != nil {
-
errorsCh <- fmt.Errorf("fetching filetree: %w", err)
-
return
-
}
-
files = fs
-
}()
-
-
wg.Wait()
-
close(errorsCh)
-
-
// show any errors
-
for err := range errorsCh {
-
l.Error("loading repo", "error", err.Error())
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
rtags := []*types.TagReference{}
-
for _, tag := range tags {
-
var target *object.Tag
-
if tag.Target != plumbing.ZeroHash {
-
target = &tag
-
}
-
tr := types.TagReference{
-
Tag: target,
-
}
-
-
tr.Reference = types.Reference{
-
Name: tag.Name,
-
Hash: tag.Hash.String(),
-
}
-
-
if tag.Message != "" {
-
tr.Message = tag.Message
-
}
-
-
rtags = append(rtags, &tr)
-
}
-
-
var readmeContent string
-
var readmeFile string
-
for _, readme := range h.c.Repo.Readme {
-
content, _ := gr.FileContent(readme)
-
if len(content) > 0 {
-
readmeContent = string(content)
-
readmeFile = readme
-
}
-
}
-
-
if ref == "" {
-
mainBranch, err := gr.FindMainBranch()
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("finding main branch", "error", err.Error())
-
return
-
}
-
ref = mainBranch
-
}
-
-
resp := types.RepoIndexResponse{
-
IsEmpty: false,
-
Ref: ref,
-
Commits: commits,
-
Description: getDescription(path),
-
Readme: readmeContent,
-
ReadmeFileName: readmeFile,
-
Files: files,
-
Branches: branches,
-
Tags: rtags,
-
TotalCommits: total,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
-
treePath := chi.URLParam(r, "*")
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
files, err := gr.FileTree(r.Context(), treePath)
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("file tree", "error", err.Error())
-
return
-
}
-
-
resp := types.RepoTreeResponse{
-
Ref: ref,
-
Parent: treePath,
-
Description: getDescription(path),
-
DotDot: filepath.Dir(treePath),
-
Files: files,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) BlobRaw(w http.ResponseWriter, r *http.Request) {
-
treePath := chi.URLParam(r, "*")
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
l := h.l.With("handler", "BlobRaw", "ref", ref, "treePath", treePath)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
contents, err := gr.RawContent(treePath)
-
if err != nil {
-
writeError(w, err.Error(), http.StatusBadRequest)
-
l.Error("file content", "error", err.Error())
-
return
-
}
-
-
mimeType := http.DetectContentType(contents)
-
-
// exception for svg
-
if filepath.Ext(treePath) == ".svg" {
-
mimeType = "image/svg+xml"
-
}
-
-
contentHash := sha256.Sum256(contents)
-
eTag := fmt.Sprintf("\"%x\"", contentHash)
-
-
// allow image, video, and text/plain files to be served directly
-
switch {
-
case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"):
-
if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag {
-
w.WriteHeader(http.StatusNotModified)
-
return
-
}
-
w.Header().Set("ETag", eTag)
-
-
case strings.HasPrefix(mimeType, "text/plain"):
-
w.Header().Set("Cache-Control", "public, no-cache")
-
-
default:
-
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
-
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
-
return
-
}
-
-
w.Header().Set("Content-Type", mimeType)
-
w.Write(contents)
-
}
-
-
func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) {
-
treePath := chi.URLParam(r, "*")
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
var isBinaryFile bool = false
-
contents, err := gr.FileContent(treePath)
-
if errors.Is(err, git.ErrBinaryFile) {
-
isBinaryFile = true
-
} else if errors.Is(err, object.ErrFileNotFound) {
-
notFound(w)
-
return
-
} else if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
bytes := []byte(contents)
-
// safe := string(sanitize(bytes))
-
sizeHint := len(bytes)
-
-
resp := types.RepoBlobResponse{
-
Ref: ref,
-
Contents: string(bytes),
-
Path: treePath,
-
IsBinary: isBinaryFile,
-
SizeHint: uint64(sizeHint),
-
}
-
-
h.showFile(resp, w, l)
-
}
-
-
func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
-
name := chi.URLParam(r, "name")
-
file := chi.URLParam(r, "file")
-
-
l := h.l.With("handler", "Archive", "name", name, "file", file)
-
-
// TODO: extend this to add more files compression (e.g.: xz)
-
if !strings.HasSuffix(file, ".tar.gz") {
-
notFound(w)
-
return
-
}
-
-
ref := strings.TrimSuffix(file, ".tar.gz")
-
-
unescapedRef, err := url.PathUnescape(ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
-
-
// This allows the browser to use a proper name for the file when
-
// downloading
-
filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename)
-
setContentDisposition(w, filename)
-
setGZipMIME(w)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.Open(path, unescapedRef)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
gw := gzip.NewWriter(w)
-
defer gw.Close()
-
-
prefix := fmt.Sprintf("%s-%s", name, safeRefFilename)
-
err = gr.WriteTar(gw, prefix)
-
if err != nil {
-
// once we start writing to the body we can't report error anymore
-
// so we are only left with printing the error.
-
l.Error("writing tar file", "error", err.Error())
-
return
-
}
-
-
err = gw.Flush()
-
if err != nil {
-
// once we start writing to the body we can't report error anymore
-
// so we are only left with printing the error.
-
l.Error("flushing?", "error", err.Error())
-
return
-
}
-
}
-
-
func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
-
l := h.l.With("handler", "Log", "ref", ref, "path", path)
-
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
// Get page parameters
-
page := 1
-
pageSize := 30
-
-
if pageParam := r.URL.Query().Get("page"); pageParam != "" {
-
if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
-
page = p
-
}
-
}
-
-
if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
-
if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
-
pageSize = ps
-
}
-
}
-
-
// convert to offset/limit
-
offset := (page - 1) * pageSize
-
limit := pageSize
-
-
commits, err := gr.Commits(offset, limit)
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("fetching commits", "error", err.Error())
-
return
-
}
-
-
total := len(commits)
-
-
resp := types.RepoLogResponse{
-
Commits: commits,
-
Ref: ref,
-
Description: getDescription(path),
-
Log: true,
-
Total: total,
-
Page: page,
-
PerPage: pageSize,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
l := h.l.With("handler", "Diff", "ref", ref)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.Open(path, ref)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
diff, err := gr.Diff()
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("getting diff", "error", err.Error())
-
return
-
}
-
-
resp := types.RepoCommitResponse{
-
Ref: ref,
-
Diff: diff,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) {
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
l := h.l.With("handler", "Refs")
-
-
gr, err := git.Open(path, "")
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
tags, err := gr.Tags()
-
if err != nil {
-
// Non-fatal, we *should* have at least one branch to show.
-
l.Warn("getting tags", "error", err.Error())
-
}
-
-
rtags := []*types.TagReference{}
-
for _, tag := range tags {
-
var target *object.Tag
-
if tag.Target != plumbing.ZeroHash {
-
target = &tag
-
}
-
tr := types.TagReference{
-
Tag: target,
-
}
-
-
tr.Reference = types.Reference{
-
Name: tag.Name,
-
Hash: tag.Hash.String(),
-
}
-
-
if tag.Message != "" {
-
tr.Message = tag.Message
-
}
-
-
rtags = append(rtags, &tr)
-
}
-
-
resp := types.RepoTagsResponse{
-
Tags: rtags,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) {
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
-
gr, err := git.PlainOpen(path)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
branches, _ := gr.Branches()
-
-
resp := types.RepoBranchesResponse{
-
Branches: branches,
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) {
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
branchName := chi.URLParam(r, "branch")
-
branchName, _ = url.PathUnescape(branchName)
-
-
l := h.l.With("handler", "Branch")
-
-
gr, err := git.PlainOpen(path)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
ref, err := gr.Branch(branchName)
-
if err != nil {
-
l.Error("getting branch", "error", err.Error())
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
commit, err := gr.Commit(ref.Hash())
-
if err != nil {
-
l.Error("getting commit object", "error", err.Error())
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
defaultBranch, err := gr.FindMainBranch()
-
isDefault := false
-
if err != nil {
-
l.Error("getting default branch", "error", err.Error())
-
// do not quit though
-
} else if defaultBranch == branchName {
-
isDefault = true
-
}
-
-
resp := types.RepoBranchResponse{
-
Branch: types.Branch{
-
Reference: types.Reference{
-
Name: ref.Name().Short(),
-
Hash: ref.Hash().String(),
-
},
-
Commit: commit,
-
IsDefault: isDefault,
-
},
-
}
-
-
writeJSON(w, resp)
-
}
-
-
func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
-
l := h.l.With("handler", "Keys")
-
-
switch r.Method {
-
case http.MethodGet:
-
keys, err := h.db.GetAllPublicKeys()
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("getting public keys", "error", err.Error())
-
return
-
}
-
-
data := make([]map[string]any, 0)
-
for _, key := range keys {
-
j := key.JSON()
-
data = append(data, j)
-
}
-
writeJSON(w, data)
-
return
-
-
case http.MethodPut:
-
pk := db.PublicKey{}
-
if err := json.NewDecoder(r.Body).Decode(&pk); err != nil {
-
writeError(w, "invalid request body", http.StatusBadRequest)
-
return
-
}
-
-
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key))
-
if err != nil {
-
writeError(w, "invalid pubkey", http.StatusBadRequest)
-
}
-
-
if err := h.db.AddPublicKey(pk); err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("adding public key", "error", err.Error())
-
return
-
}
-
-
w.WriteHeader(http.StatusNoContent)
-
return
-
}
-
}
-
-
// func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
-
// l := h.l.With("handler", "RepoForkSync")
-
//
-
// data := struct {
-
// Did string `json:"did"`
-
// Source string `json:"source"`
-
// Name string `json:"name,omitempty"`
-
// HiddenRef string `json:"hiddenref"`
-
// }{}
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, "invalid request body", http.StatusBadRequest)
-
// return
-
// }
-
//
-
// did := data.Did
-
// source := data.Source
-
//
-
// if did == "" || source == "" {
-
// l.Error("invalid request body, empty did or name")
-
// w.WriteHeader(http.StatusBadRequest)
-
// return
-
// }
-
//
-
// var name string
-
// if data.Name != "" {
-
// name = data.Name
-
// } else {
-
// name = filepath.Base(source)
-
// }
-
//
-
// branch := chi.URLParam(r, "branch")
-
// branch, _ = url.PathUnescape(branch)
-
//
-
// relativeRepoPath := filepath.Join(did, name)
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
-
//
-
// gr, err := git.PlainOpen(repoPath)
-
// if err != nil {
-
// log.Println(err)
-
// notFound(w)
-
// return
-
// }
-
//
-
// forkCommit, err := gr.ResolveRevision(branch)
-
// if err != nil {
-
// l.Error("error resolving ref revision", "msg", err.Error())
-
// writeError(w, fmt.Sprintf("error resolving revision %s", branch), http.StatusBadRequest)
-
// return
-
// }
-
//
-
// sourceCommit, err := gr.ResolveRevision(data.HiddenRef)
-
// if err != nil {
-
// l.Error("error resolving hidden ref revision", "msg", err.Error())
-
// writeError(w, fmt.Sprintf("error resolving revision %s", data.HiddenRef), http.StatusBadRequest)
-
// return
-
// }
-
//
-
// status := types.UpToDate
-
// if forkCommit.Hash.String() != sourceCommit.Hash.String() {
-
// isAncestor, err := forkCommit.IsAncestor(sourceCommit)
-
// if err != nil {
-
// log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
-
// return
-
// }
-
//
-
// if isAncestor {
-
// status = types.FastForwardable
-
// } else {
-
// status = types.Conflict
-
// }
-
// }
-
//
-
// w.Header().Set("Content-Type", "application/json")
-
// json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status})
-
// }
-
-
func (h *Handle) RepoLanguages(w http.ResponseWriter, r *http.Request) {
-
repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
-
l := h.l.With("handler", "RepoLanguages")
-
-
gr, err := git.Open(repoPath, ref)
-
if err != nil {
-
l.Error("opening repo", "error", err.Error())
-
notFound(w)
-
return
-
}
-
-
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
-
defer cancel()
-
-
sizes, err := gr.AnalyzeLanguages(ctx)
-
if err != nil {
-
l.Error("failed to analyze languages", "error", err.Error())
-
writeError(w, err.Error(), http.StatusNoContent)
-
return
-
}
-
-
resp := types.RepoLanguageResponse{Languages: sizes}
-
-
writeJSON(w, resp)
-
}
-
-
// func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
-
// l := h.l.With("handler", "RepoForkSync")
-
//
-
// data := struct {
-
// Did string `json:"did"`
-
// Source string `json:"source"`
-
// Name string `json:"name,omitempty"`
-
// }{}
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, "invalid request body", http.StatusBadRequest)
-
// return
-
// }
-
//
-
// did := data.Did
-
// source := data.Source
-
//
-
// if did == "" || source == "" {
-
// l.Error("invalid request body, empty did or name")
-
// w.WriteHeader(http.StatusBadRequest)
-
// return
-
// }
-
//
-
// var name string
-
// if data.Name != "" {
-
// name = data.Name
-
// } else {
-
// name = filepath.Base(source)
-
// }
-
//
-
// branch := chi.URLParam(r, "branch")
-
// branch, _ = url.PathUnescape(branch)
-
//
-
// relativeRepoPath := filepath.Join(did, name)
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
-
//
-
// gr, err := git.Open(repoPath, branch)
-
// if err != nil {
-
// log.Println(err)
-
// notFound(w)
-
// return
-
// }
-
//
-
// err = gr.Sync()
-
// if err != nil {
-
// l.Error("error syncing repo fork", "error", err.Error())
-
// writeError(w, err.Error(), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// w.WriteHeader(http.StatusNoContent)
-
// }
-
-
// func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) {
-
// l := h.l.With("handler", "RepoFork")
-
//
-
// data := struct {
-
// Did string `json:"did"`
-
// Source string `json:"source"`
-
// Name string `json:"name,omitempty"`
-
// }{}
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, "invalid request body", http.StatusBadRequest)
-
// return
-
// }
-
//
-
// did := data.Did
-
// source := data.Source
-
//
-
// if did == "" || source == "" {
-
// l.Error("invalid request body, empty did or name")
-
// w.WriteHeader(http.StatusBadRequest)
-
// return
-
// }
-
//
-
// var name string
-
// if data.Name != "" {
-
// name = data.Name
-
// } else {
-
// name = filepath.Base(source)
-
// }
-
//
-
// relativeRepoPath := filepath.Join(did, name)
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
-
//
-
// err := git.Fork(repoPath, source)
-
// if err != nil {
-
// l.Error("forking repo", "error", err.Error())
-
// writeError(w, err.Error(), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// // add perms for this user to access the repo
-
// err = h.e.AddRepo(did, rbac.ThisServer, relativeRepoPath)
-
// if err != nil {
-
// l.Error("adding repo permissions", "error", err.Error())
-
// writeError(w, err.Error(), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// hook.SetupRepo(
-
// hook.Config(
-
// hook.WithScanPath(h.c.Repo.ScanPath),
-
// hook.WithInternalApi(h.c.Server.InternalListenAddr),
-
// ),
-
// repoPath,
-
// )
-
//
-
// w.WriteHeader(http.StatusNoContent)
-
// }
-
-
// func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
-
// l := h.l.With("handler", "RemoveRepo")
-
//
-
// data := struct {
-
// Did string `json:"did"`
-
// Name string `json:"name"`
-
// }{}
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, "invalid request body", http.StatusBadRequest)
-
// return
-
// }
-
//
-
// did := data.Did
-
// name := data.Name
-
//
-
// if did == "" || name == "" {
-
// l.Error("invalid request body, empty did or name")
-
// w.WriteHeader(http.StatusBadRequest)
-
// return
-
// }
-
//
-
// relativeRepoPath := filepath.Join(did, name)
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
-
// err := os.RemoveAll(repoPath)
-
// if err != nil {
-
// l.Error("removing repo", "error", err.Error())
-
// writeError(w, err.Error(), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// w.WriteHeader(http.StatusNoContent)
-
//
-
// }
-
-
// func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
-
// path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
//
-
// data := types.MergeRequest{}
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, err.Error(), http.StatusBadRequest)
-
// h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
-
// return
-
// }
-
//
-
// mo := &git.MergeOptions{
-
// AuthorName: data.AuthorName,
-
// AuthorEmail: data.AuthorEmail,
-
// CommitBody: data.CommitBody,
-
// CommitMessage: data.CommitMessage,
-
// }
-
//
-
// patch := data.Patch
-
// branch := data.Branch
-
// gr, err := git.Open(path, branch)
-
// if err != nil {
-
// notFound(w)
-
// return
-
// }
-
//
-
// mo.FormatPatch = patchutil.IsFormatPatch(patch)
-
//
-
// if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
-
// var mergeErr *git.ErrMerge
-
// if errors.As(err, &mergeErr) {
-
// conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
-
// for i, conflict := range mergeErr.Conflicts {
-
// conflicts[i] = types.ConflictInfo{
-
// Filename: conflict.Filename,
-
// Reason: conflict.Reason,
-
// }
-
// }
-
// response := types.MergeCheckResponse{
-
// IsConflicted: true,
-
// Conflicts: conflicts,
-
// Message: mergeErr.Message,
-
// }
-
// writeConflict(w, response)
-
// h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
-
// } else {
-
// writeError(w, err.Error(), http.StatusBadRequest)
-
// h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
-
// }
-
// return
-
// }
-
//
-
// w.WriteHeader(http.StatusOK)
-
// }
-
-
// func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
-
// path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
//
-
// var data struct {
-
// Patch string `json:"patch"`
-
// Branch string `json:"branch"`
-
// }
-
//
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// writeError(w, err.Error(), http.StatusBadRequest)
-
// h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
-
// return
-
// }
-
//
-
// patch := data.Patch
-
// branch := data.Branch
-
// gr, err := git.Open(path, branch)
-
// if err != nil {
-
// notFound(w)
-
// return
-
// }
-
//
-
// err = gr.MergeCheck([]byte(patch), branch)
-
// if err == nil {
-
// response := types.MergeCheckResponse{
-
// IsConflicted: false,
-
// }
-
// writeJSON(w, response)
-
// return
-
// }
-
//
-
// var mergeErr *git.ErrMerge
-
// if errors.As(err, &mergeErr) {
-
// conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
-
// for i, conflict := range mergeErr.Conflicts {
-
// conflicts[i] = types.ConflictInfo{
-
// Filename: conflict.Filename,
-
// Reason: conflict.Reason,
-
// }
-
// }
-
// response := types.MergeCheckResponse{
-
// IsConflicted: true,
-
// Conflicts: conflicts,
-
// Message: mergeErr.Message,
-
// }
-
// writeConflict(w, response)
-
// h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
-
// return
-
// }
-
// writeError(w, err.Error(), http.StatusInternalServerError)
-
// h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
-
// }
-
-
func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
-
rev1 := chi.URLParam(r, "rev1")
-
rev1, _ = url.PathUnescape(rev1)
-
-
rev2 := chi.URLParam(r, "rev2")
-
rev2, _ = url.PathUnescape(rev2)
-
-
l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
-
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
gr, err := git.PlainOpen(path)
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
commit1, err := gr.ResolveRevision(rev1)
-
if err != nil {
-
l.Error("error resolving revision 1", "msg", err.Error())
-
writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
-
return
-
}
-
-
commit2, err := gr.ResolveRevision(rev2)
-
if err != nil {
-
l.Error("error resolving revision 2", "msg", err.Error())
-
writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
-
return
-
}
-
-
rawPatch, formatPatch, err := gr.FormatPatch(commit1, commit2)
-
if err != nil {
-
l.Error("error comparing revisions", "msg", err.Error())
-
writeError(w, "error comparing revisions", http.StatusBadRequest)
-
return
-
}
-
-
writeJSON(w, types.RepoFormatPatchResponse{
-
Rev1: commit1.Hash.String(),
-
Rev2: commit2.Hash.String(),
-
FormatPatch: formatPatch,
-
Patch: rawPatch,
-
})
-
}
-
-
func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
-
l := h.l.With("handler", "DefaultBranch")
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
-
-
gr, err := git.Open(path, "")
-
if err != nil {
-
notFound(w)
-
return
-
}
-
-
branch, err := gr.FindMainBranch()
-
if err != nil {
-
writeError(w, err.Error(), http.StatusInternalServerError)
-
l.Error("getting default branch", "error", err.Error())
-
return
-
}
-
-
writeJSON(w, types.RepoDefaultBranchResponse{
-
Branch: branch,
-
})
-
}
+6 -6
knotserver/ingester.go
···
"tangled.sh/tangled.sh/core/workflow"
)
-
func (h *Handle) processPublicKey(ctx context.Context, event *models.Event) error {
+
func (h *Knot) processPublicKey(ctx context.Context, event *models.Event) error {
l := log.FromContext(ctx)
raw := json.RawMessage(event.Commit.Record)
did := event.Did
···
return nil
}
-
func (h *Handle) processKnotMember(ctx context.Context, event *models.Event) error {
+
func (h *Knot) processKnotMember(ctx context.Context, event *models.Event) error {
l := log.FromContext(ctx)
raw := json.RawMessage(event.Commit.Record)
did := event.Did
···
return nil
}
-
func (h *Handle) processPull(ctx context.Context, event *models.Event) error {
+
func (h *Knot) processPull(ctx context.Context, event *models.Event) error {
raw := json.RawMessage(event.Commit.Record)
did := event.Did
···
}
// duplicated from add collaborator
-
func (h *Handle) processCollaborator(ctx context.Context, event *models.Event) error {
+
func (h *Knot) processCollaborator(ctx context.Context, event *models.Event) error {
raw := json.RawMessage(event.Commit.Record)
did := event.Did
···
return h.fetchAndAddKeys(ctx, subjectId.DID.String())
}
-
func (h *Handle) fetchAndAddKeys(ctx context.Context, did string) error {
+
func (h *Knot) fetchAndAddKeys(ctx context.Context, did string) error {
l := log.FromContext(ctx)
keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did)
···
return nil
}
-
func (h *Handle) processMessages(ctx context.Context, event *models.Event) error {
+
func (h *Knot) processMessages(ctx context.Context, event *models.Event) error {
if event.Kind != models.EventKindCommit {
return nil
}
+20 -46
knotserver/routes.go knotserver/router.go
···
"tangled.sh/tangled.sh/core/xrpc/serviceauth"
)
-
type Handle struct {
+
type Knot struct {
c *config.Config
db *db.DB
jc *jetstream.JetstreamClient
···
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) {
r := chi.NewRouter()
-
h := Handle{
+
h := Knot{
c: c,
db: db,
e: e,
···
return nil, fmt.Errorf("failed to start jetstream: %w", err)
}
-
r.Get("/", h.Index)
-
r.Get("/capabilities", h.Capabilities)
-
r.Get("/version", h.Version)
-
r.Get("/owner", func(w http.ResponseWriter, r *http.Request) {
+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+
w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
+
})
+
+
owner := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(h.c.Server.Owner))
-
})
+
}
+
// Deprecated: the sh.tangled.knot.owner xrpc call should be used instead
+
r.Get("/owner", owner)
+
r.Route("/{did}", func(r chi.Router) {
-
// Repo routes
r.Route("/{name}", func(r chi.Router) {
-
-
r.Route("/languages", func(r chi.Router) {
-
r.Get("/", h.RepoLanguages)
-
r.Get("/{ref}", h.RepoLanguages)
-
})
-
-
r.Get("/", h.RepoIndex)
+
// routes for git operations
r.Get("/info/refs", h.InfoRefs)
r.Post("/git-upload-pack", h.UploadPack)
r.Post("/git-receive-pack", h.ReceivePack)
-
r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
-
-
r.Route("/tree/{ref}", func(r chi.Router) {
-
r.Get("/", h.RepoIndex)
-
r.Get("/*", h.RepoTree)
-
})
-
-
r.Route("/blob/{ref}", func(r chi.Router) {
-
r.Get("/*", h.Blob)
-
})
-
-
r.Route("/raw/{ref}", func(r chi.Router) {
-
r.Get("/*", h.BlobRaw)
-
})
-
-
r.Get("/log/{ref}", h.Log)
-
r.Get("/archive/{file}", h.Archive)
-
r.Get("/commit/{ref}", h.Diff)
-
r.Get("/tags", h.Tags)
-
r.Route("/branches", func(r chi.Router) {
-
r.Get("/", h.Branches)
-
r.Get("/{branch}", h.Branch)
-
r.Get("/default", h.DefaultBranch)
-
})
})
})
// xrpc apis
-
r.Mount("/xrpc", h.XrpcRouter())
+
r.Route("/xrpc", func(r chi.Router) {
+
r.Get("/_health", h.Version)
+
r.Get("/sh.tangled.knot.owner", owner)
+
r.Mount("/", h.XrpcRouter())
+
})
// Socket that streams git oplogs
r.Get("/events", h.Events)
-
// All public keys on the knot.
-
r.Get("/keys", h.Keys)
-
return r, nil
}
-
func (h *Handle) XrpcRouter() http.Handler {
+
func (h *Knot) XrpcRouter() http.Handler {
logger := tlog.New("knots")
serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String())
···
// version is set during build time.
var version string
-
func (h *Handle) Version(w http.ResponseWriter, r *http.Request) {
+
func (h *Knot) Version(w http.ResponseWriter, r *http.Request) {
if version == "" {
info, ok := debug.ReadBuildInfo()
if !ok {
···
fmt.Fprintf(w, "knotserver/%s", version)
}
-
func (h *Handle) configureOwner() error {
+
func (h *Knot) configureOwner() error {
cfgOwner := h.c.Server.Owner
rbacDomain := "thisserver"