1package xrpc
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/url"
7 "path/filepath"
8
9 "tangled.sh/tangled.sh/core/api/tangled"
10 "tangled.sh/tangled.sh/core/knotserver/git"
11 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
12)
13
14func (x *Xrpc) RepoTree(w http.ResponseWriter, r *http.Request) {
15 ctx := r.Context()
16
17 repo := r.URL.Query().Get("repo")
18 repoPath, err := x.parseRepoParam(repo)
19 if err != nil {
20 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
21 return
22 }
23
24 refParam := r.URL.Query().Get("ref")
25 if refParam == "" {
26 writeError(w, xrpcerr.NewXrpcError(
27 xrpcerr.WithTag("InvalidRequest"),
28 xrpcerr.WithMessage("missing ref parameter"),
29 ), http.StatusBadRequest)
30 return
31 }
32
33 path := r.URL.Query().Get("path")
34 // path can be empty (defaults to root)
35
36 ref, err := url.QueryUnescape(refParam)
37 if err != nil {
38 writeError(w, xrpcerr.NewXrpcError(
39 xrpcerr.WithTag("InvalidRequest"),
40 xrpcerr.WithMessage("invalid ref parameter"),
41 ), http.StatusBadRequest)
42 return
43 }
44
45 gr, err := git.Open(repoPath, ref)
46 if err != nil {
47 x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref)
48 writeError(w, xrpcerr.NewXrpcError(
49 xrpcerr.WithTag("RefNotFound"),
50 xrpcerr.WithMessage("repository or ref not found"),
51 ), http.StatusNotFound)
52 return
53 }
54
55 files, err := gr.FileTree(ctx, path)
56 if err != nil {
57 x.Logger.Error("failed to get file tree", "error", err, "path", path)
58 writeError(w, xrpcerr.NewXrpcError(
59 xrpcerr.WithTag("PathNotFound"),
60 xrpcerr.WithMessage("failed to read repository tree"),
61 ), http.StatusNotFound)
62 return
63 }
64
65 // convert NiceTree -> tangled.RepoTree_TreeEntry
66 treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
67 for i, file := range files {
68 entry := &tangled.RepoTree_TreeEntry{
69 Name: file.Name,
70 Mode: file.Mode,
71 Size: file.Size,
72 Is_file: file.IsFile,
73 Is_subtree: file.IsSubtree,
74 }
75
76 if file.LastCommit != nil {
77 entry.Last_commit = &tangled.RepoTree_LastCommit{
78 Hash: file.LastCommit.Hash.String(),
79 Message: file.LastCommit.Message,
80 When: file.LastCommit.When.Format("2006-01-02T15:04:05.000Z"),
81 }
82 }
83
84 treeEntries[i] = entry
85 }
86
87 var parentPtr *string
88 if path != "" {
89 parentPtr = &path
90 }
91
92 var dotdotPtr *string
93 if path != "" {
94 dotdot := filepath.Dir(path)
95 if dotdot != "." {
96 dotdotPtr = &dotdot
97 }
98 }
99
100 response := tangled.RepoTree_Output{
101 Ref: ref,
102 Parent: parentPtr,
103 Dotdot: dotdotPtr,
104 Files: treeEntries,
105 }
106
107 w.Header().Set("Content-Type", "application/json")
108 if err := json.NewEncoder(w).Encode(response); err != nil {
109 x.Logger.Error("failed to encode response", "error", err)
110 writeError(w, xrpcerr.NewXrpcError(
111 xrpcerr.WithTag("InternalServerError"),
112 xrpcerr.WithMessage("failed to encode response"),
113 ), http.StatusInternalServerError)
114 return
115 }
116}