1package xrpc
2
3import (
4 "net/http"
5 "path/filepath"
6 "time"
7 "unicode/utf8"
8
9 "tangled.org/core/api/tangled"
10 "tangled.org/core/appview/pages/markup"
11 "tangled.org/core/knotserver/git"
12 xrpcerr "tangled.org/core/xrpc/errors"
13)
14
15func (x *Xrpc) RepoTree(w http.ResponseWriter, r *http.Request) {
16 ctx := r.Context()
17
18 repo := r.URL.Query().Get("repo")
19 repoPath, err := x.parseRepoParam(repo)
20 if err != nil {
21 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
22 return
23 }
24
25 ref := r.URL.Query().Get("ref")
26 // ref can be empty (git.Open handles this)
27
28 path := r.URL.Query().Get("path")
29 // path can be empty (defaults to root)
30
31 gr, err := git.Open(repoPath, ref)
32 if err != nil {
33 x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref)
34 writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
35 return
36 }
37
38 files, err := gr.FileTree(ctx, path)
39 if err != nil {
40 x.Logger.Error("failed to get file tree", "error", err, "path", path)
41 writeError(w, xrpcerr.NewXrpcError(
42 xrpcerr.WithTag("PathNotFound"),
43 xrpcerr.WithMessage("failed to read repository tree"),
44 ), http.StatusNotFound)
45 return
46 }
47
48 // if any of these files are a readme candidate, pass along its blob contents too
49 var readmeFileName string
50 var readmeContents string
51 for _, file := range files {
52 if markup.IsReadmeFile(file.Name) {
53 contents, err := gr.RawContent(filepath.Join(path, file.Name))
54 if err != nil {
55 x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name)
56 }
57
58 if utf8.Valid(contents) {
59 readmeFileName = file.Name
60 readmeContents = string(contents)
61 break
62 }
63 }
64 }
65
66 // convert NiceTree -> tangled.RepoTree_TreeEntry
67 treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
68 for i, file := range files {
69 entry := &tangled.RepoTree_TreeEntry{
70 Name: file.Name,
71 Mode: file.Mode,
72 Size: file.Size,
73 Is_file: file.IsFile,
74 Is_subtree: file.IsSubtree,
75 }
76
77 if file.LastCommit != nil {
78 entry.Last_commit = &tangled.RepoTree_LastCommit{
79 Hash: file.LastCommit.Hash.String(),
80 Message: file.LastCommit.Message,
81 When: file.LastCommit.When.Format(time.RFC3339),
82 }
83 }
84
85 treeEntries[i] = entry
86 }
87
88 var parentPtr *string
89 if path != "" {
90 parentPtr = &path
91 }
92
93 var dotdotPtr *string
94 if path != "" {
95 dotdot := filepath.Dir(path)
96 if dotdot != "." {
97 dotdotPtr = &dotdot
98 }
99 }
100
101 response := tangled.RepoTree_Output{
102 Ref: ref,
103 Parent: parentPtr,
104 Dotdot: dotdotPtr,
105 Files: treeEntries,
106 Readme: &tangled.RepoTree_Readme{
107 Filename: readmeFileName,
108 Contents: readmeContents,
109 },
110 }
111
112 writeJson(w, response)
113}