This repository has no description
4.1 kB
152 lines
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 "tangled.org/core/types"
17)
18
19func (x *Xrpc) GetTree(w http.ResponseWriter, r *http.Request) {
20 var (
21 repoQuery = r.URL.Query().Get("repo")
22 ref = r.URL.Query().Get("ref") // ref can be empty (git.Open handles this)
23 path = r.URL.Query().Get("path") // path can be empty (defaults to root)
24 )
25 l := x.logger.With("method", "git.getTree", "repo", repoQuery, "ref", ref)
26 l.Debug("request")
27
28 repo, err := syntax.ParseATURI(repoQuery)
29 if err != nil || repo.RecordKey() == "" {
30 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
31 return
32 }
33
34 out, err := x.getTree(r.Context(), repo, ref, path)
35 if err != nil {
36 l.Warn("local mirror failed, trying proxy", "repo", repo, "err", err)
37 if x.proxyToKnot(w, r, repo) {
38 return
39 }
40 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get tree"})
41 return
42 }
43 writeJson(w, http.StatusOK, out)
44}
45
46func (x *Xrpc) getTree(ctx context.Context, repo syntax.ATURI, ref, path string) (*tangled.GitTempGetTree_Output, error) {
47 repoPath, err := x.makeRepoPath(ctx, repo)
48 if err != nil {
49 return nil, fmt.Errorf("failed to resolve repo at-uri: %w", err)
50 }
51
52 gr, err := git.Open(repoPath, ref)
53 if err != nil {
54 return nil, fmt.Errorf("opening git repo: %w", err)
55 }
56
57 files, err := gr.FileTree(ctx, path)
58 if err != nil {
59 return nil, fmt.Errorf("reading file tree: %w", err)
60 }
61
62 // if any of these files are a readme candidate, pass along its blob contents too
63 var readmeFileName string
64 var readmeContents string
65 for _, file := range files {
66 if markup.IsReadmeFile(file.Name) {
67 contents, err := gr.RawContent(filepath.Join(path, file.Name))
68 if err != nil {
69 x.logger.Error("failed to read contents of file", "path", path, "file", file.Name)
70 }
71
72 if utf8.Valid(contents) {
73 readmeFileName = file.Name
74 readmeContents = string(contents)
75 break
76 }
77 }
78 }
79
80 // convert NiceTree -> tangled.RepoTempGetTree_TreeEntry
81 treeEntries := make([]*tangled.GitTempGetTree_TreeEntry, len(files))
82 for i, file := range files {
83 entry := &tangled.GitTempGetTree_TreeEntry{
84 Name: file.Name,
85 Mode: file.Mode,
86 Size: file.Size,
87 }
88 if file.LastCommit != nil {
89 entry.Last_commit = &tangled.GitTempGetTree_LastCommit{
90 Hash: file.LastCommit.Hash.String(),
91 Message: file.LastCommit.Message,
92 When: file.LastCommit.When.Format(time.RFC3339),
93 }
94 }
95 treeEntries[i] = entry
96 }
97
98 var parentPtr *string
99 if path != "" {
100 parentPtr = &path
101 }
102
103 var dotdotPtr *string
104 if path != "" {
105 dotdot := filepath.Dir(path)
106 if dotdot != "." {
107 dotdotPtr = &dotdot
108 }
109 }
110
111 // find the most recent commit across all entries for the directory-level last commit
112 var lastCommitInfo *types.LastCommitInfo
113 for _, file := range files {
114 if file.LastCommit == nil {
115 continue
116 }
117 if lastCommitInfo == nil {
118 lastCommitInfo = file.LastCommit
119 continue
120 }
121 if file.LastCommit.When.After(lastCommitInfo.When) {
122 lastCommitInfo = file.LastCommit
123 }
124 }
125
126 var lastCommit *tangled.GitTempGetTree_LastCommit
127 if lastCommitInfo != nil {
128 lastCommit = &tangled.GitTempGetTree_LastCommit{
129 Hash: lastCommitInfo.Hash.String(),
130 Message: lastCommitInfo.Message,
131 When: lastCommitInfo.When.Format(time.RFC3339),
132 }
133 if commit, err := gr.Commit(lastCommitInfo.Hash); err == nil {
134 lastCommit.Author = &tangled.GitTempGetTree_Signature{
135 Name: commit.Author.Name,
136 Email: commit.Author.Email,
137 }
138 }
139 }
140
141 return &tangled.GitTempGetTree_Output{
142 Ref: ref,
143 Parent: parentPtr,
144 Dotdot: dotdotPtr,
145 Files: treeEntries,
146 LastCommit: lastCommit,
147 Readme: &tangled.GitTempGetTree_Readme{
148 Filename: readmeFileName,
149 Contents: readmeContents,
150 },
151 }, nil
152}