A vibe coded tangled fork which supports pijul.
1package xrpc
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "path/filepath"
8 "time"
9 "unicode/utf8"
10
11 "github.com/bluesky-social/indigo/atproto/atclient"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "tangled.org/core/api/tangled"
14 "tangled.org/core/appview/pages/markup"
15 "tangled.org/core/knotserver/git"
16)
17
18func (x *Xrpc) GetTree(w http.ResponseWriter, r *http.Request) {
19 var (
20 repoQuery = r.URL.Query().Get("repo")
21 ref = r.URL.Query().Get("ref") // ref can be empty (git.Open handles this)
22 path = r.URL.Query().Get("path") // path can be empty (defaults to root)
23 )
24
25 repo, err := syntax.ParseATURI(repoQuery)
26 if err != nil || repo.RecordKey() == "" {
27 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
28 return
29 }
30
31 l := x.logger.With("repo", repo, "ref", ref, "path", path)
32
33 out, err := x.getTree(r.Context(), repo, ref, path)
34 if err != nil {
35 // TODO: better error return
36 l.Error("failed to get tree", "err", err)
37 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get tree"})
38 return
39 }
40 writeJson(w, http.StatusOK, out)
41}
42
43func (x *Xrpc) getTree(ctx context.Context, repo syntax.ATURI, ref, path string) (*tangled.GitTempGetTree_Output, error) {
44 repoPath, err := x.makeRepoPath(ctx, repo)
45 if err != nil {
46 return nil, fmt.Errorf("failed to resolve repo at-uri: %w", err)
47 }
48
49 gr, err := git.Open(repoPath, ref)
50 if err != nil {
51 return nil, fmt.Errorf("opening git repo: %w", err)
52 }
53
54 files, err := gr.FileTree(ctx, path)
55 if err != nil {
56 return nil, fmt.Errorf("reading file tree: %w", err)
57 }
58
59 // if any of these files are a readme candidate, pass along its blob contents too
60 var readmeFileName string
61 var readmeContents string
62 for _, file := range files {
63 if markup.IsReadmeFile(file.Name) {
64 contents, err := gr.RawContent(filepath.Join(path, file.Name))
65 if err != nil {
66 x.logger.Error("failed to read contents of file", "path", path, "file", file.Name)
67 }
68
69 if utf8.Valid(contents) {
70 readmeFileName = file.Name
71 readmeContents = string(contents)
72 break
73 }
74 }
75 }
76
77 // convert NiceTree -> tangled.RepoTempGetTree_TreeEntry
78 treeEntries := make([]*tangled.GitTempGetTree_TreeEntry, len(files))
79 for i, file := range files {
80 entry := &tangled.GitTempGetTree_TreeEntry{
81 Name: file.Name,
82 Mode: file.Mode,
83 Size: file.Size,
84 }
85 if file.LastCommit != nil {
86 entry.Last_commit = &tangled.GitTempGetTree_LastCommit{
87 Hash: file.LastCommit.Hash.String(),
88 Message: file.LastCommit.Message,
89 When: file.LastCommit.When.Format(time.RFC3339),
90 }
91 }
92 treeEntries[i] = entry
93 }
94
95 var parentPtr *string
96 if path != "" {
97 parentPtr = &path
98 }
99
100 var dotdotPtr *string
101 if path != "" {
102 dotdot := filepath.Dir(path)
103 if dotdot != "." {
104 dotdotPtr = &dotdot
105 }
106 }
107
108 return &tangled.GitTempGetTree_Output{
109 Ref: ref,
110 Parent: parentPtr,
111 Dotdot: dotdotPtr,
112 Files: treeEntries,
113 Readme: &tangled.GitTempGetTree_Readme{
114 Filename: readmeFileName,
115 Contents: readmeContents,
116 },
117 }, nil
118}