forked from
tangled.org/core
Monorepo for Tangled
1package xrpc
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "path/filepath"
8 "strconv"
9
10 "github.com/bluesky-social/indigo/atproto/atclient"
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "tangled.org/core/knotmirror/db"
13 "tangled.org/core/knotserver/git"
14 "tangled.org/core/types"
15)
16
17func (x *Xrpc) ListBranches(w http.ResponseWriter, r *http.Request) {
18 var (
19 repoQuery = r.URL.Query().Get("repo")
20 limitQuery = r.URL.Query().Get("limit")
21 cursorQuery = r.URL.Query().Get("cursor")
22 )
23
24 repo, err := syntax.ParseATURI(repoQuery)
25 if err != nil || repo.RecordKey() == "" {
26 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
27 return
28 }
29
30 limit := 50
31 if limitQuery != "" {
32 limit, err = strconv.Atoi(limitQuery)
33 if err != nil || limit < 1 || limit > 1000 {
34 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("limit parameter invalid: %s", limitQuery)})
35 return
36 }
37 }
38
39 var cursor int64
40 if cursorQuery != "" {
41 cursor, err = strconv.ParseInt(cursorQuery, 10, 64)
42 if err != nil || cursor < 0 {
43 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("cursor parameter invalid: %s", cursorQuery)})
44 return
45 }
46 }
47
48 out, err := x.listBranches(r.Context(), repo, limit, cursor)
49 if err != nil {
50 x.logger.Warn("local mirror failed, trying proxy", "repo", repo, "err", err)
51 if x.proxyToKnot(w, r, repo) {
52 return
53 }
54 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to list branches"})
55 return
56 }
57 writeJson(w, http.StatusOK, out)
58}
59
60func (x *Xrpc) listBranches(ctx context.Context, repo syntax.ATURI, limit int, cursor int64) (*types.RepoBranchesResponse, error) {
61 repoPath, err := x.makeRepoPath(ctx, repo)
62 if err != nil {
63 return nil, fmt.Errorf("resolving repo at-uri: %w", err)
64 }
65
66 gr, err := git.PlainOpen(repoPath)
67 if err != nil {
68 return nil, fmt.Errorf("opening git repo: %w", err)
69 }
70
71 branches, err := gr.Branches(&git.BranchesOptions{
72 Limit: limit,
73 Offset: int(cursor),
74 })
75 if err != nil {
76 return nil, fmt.Errorf("listing git branches: %w", err)
77 }
78
79 return &types.RepoBranchesResponse{
80 // TODO: include default branch and cursor
81 Branches: branches,
82 }, nil
83}
84
85func (x *Xrpc) makeRepoPath(ctx context.Context, repo syntax.ATURI) (string, error) {
86 r, err := db.GetRepoByAtUri(ctx, x.db, repo)
87 if err != nil {
88 return "", fmt.Errorf("looking up repo: %w", err)
89 }
90 if r == nil {
91 return "", fmt.Errorf("repo not found: %s", repo)
92 }
93 if r.RepoDid == "" {
94 return "", fmt.Errorf("repo missing repo_did: %s", repo)
95 }
96 return filepath.Join(x.cfg.GitRepoBasePath, r.RepoDid.String()), nil
97}