A vibe coded tangled fork which supports pijul.
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/knotserver/git"
13 "tangled.org/core/types"
14)
15
16func (x *Xrpc) ListBranches(w http.ResponseWriter, r *http.Request) {
17 var (
18 repoQuery = r.URL.Query().Get("repo")
19 limitQuery = r.URL.Query().Get("limit")
20 cursorQuery = r.URL.Query().Get("cursor")
21 )
22
23 repo, err := syntax.ParseATURI(repoQuery)
24 if err != nil || repo.RecordKey() == "" {
25 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
26 return
27 }
28
29 limit := 50
30 if limitQuery != "" {
31 limit, err = strconv.Atoi(limitQuery)
32 if err != nil || limit < 1 || limit > 1000 {
33 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("limit parameter invalid: %s", limitQuery)})
34 return
35 }
36 }
37
38 var cursor int64
39 if cursorQuery != "" {
40 cursor, err = strconv.ParseInt(cursorQuery, 10, 64)
41 if err != nil || cursor < 0 {
42 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("cursor parameter invalid: %s", cursorQuery)})
43 return
44 }
45 }
46
47 l := x.logger.With("repo", repoQuery, "limit", limit, "cursor", cursor)
48
49 out, err := x.listBranches(r.Context(), repo, limit, cursor)
50 if err != nil {
51 // TODO: better error return
52 l.Error("failed to list branches", "err", err)
53 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to list branches"})
54 return
55 }
56 writeJson(w, http.StatusOK, out)
57}
58
59func (x *Xrpc) listBranches(ctx context.Context, repo syntax.ATURI, limit int, cursor int64) (*types.RepoBranchesResponse, error) {
60 repoPath, err := x.makeRepoPath(ctx, repo)
61 if err != nil {
62 return nil, fmt.Errorf("resolving repo at-uri: %w", err)
63 }
64
65 gr, err := git.PlainOpen(repoPath)
66 if err != nil {
67 return nil, fmt.Errorf("opening git repo: %w", err)
68 }
69
70 branches, err := gr.Branches(&git.BranchesOptions{
71 Limit: limit,
72 Offset: int(cursor),
73 })
74 if err != nil {
75 return nil, fmt.Errorf("listing git branches: %w", err)
76 }
77
78 return &types.RepoBranchesResponse{
79 // TODO: include default branch and cursor
80 Branches: branches,
81 }, nil
82}
83
84func (x *Xrpc) makeRepoPath(ctx context.Context, repo syntax.ATURI) (string, error) {
85 id, err := x.resolver.ResolveIdent(ctx, repo.Authority().String())
86 if err != nil {
87 return "", err
88 }
89
90 return filepath.Join(
91 x.cfg.GitRepoBasePath,
92 id.DID.String(),
93 repo.RecordKey().String(),
94 ), nil
95}