This repository has no description
3.2 kB
119 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)
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 l := x.logger.With("method", "git.getTree", "repo", repoQuery, "ref", ref)
25
26 repo, err := syntax.ParseATURI(repoQuery)
27 if err != nil || repo.RecordKey() == "" {
28 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
29 return
30 }
31
32 out, err := x.getTree(r.Context(), repo, ref, path)
33 if err != nil {
34 l.Warn("local mirror failed, trying proxy", "repo", repo, "err", err)
35 if x.proxyToKnot(w, r, repo) {
36 return
37 }
38 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get tree"})
39 return
40 }
41 writeJson(w, http.StatusOK, out)
42}
43
44func (x *Xrpc) getTree(ctx context.Context, repo syntax.ATURI, ref, path string) (*tangled.GitTempGetTree_Output, error) {
45 repoPath, err := x.makeRepoPath(ctx, repo)
46 if err != nil {
47 return nil, fmt.Errorf("failed to resolve repo at-uri: %w", err)
48 }
49
50 gr, err := git.Open(repoPath, ref)
51 if err != nil {
52 return nil, fmt.Errorf("opening git repo: %w", err)
53 }
54
55 files, err := gr.FileTree(ctx, path)
56 if err != nil {
57 return nil, fmt.Errorf("reading file tree: %w", err)
58 }
59
60 // if any of these files are a readme candidate, pass along its blob contents too
61 var readmeFileName string
62 var readmeContents string
63 for _, file := range files {
64 if markup.IsReadmeFile(file.Name) {
65 contents, err := gr.RawContent(filepath.Join(path, file.Name))
66 if err != nil {
67 x.logger.Error("failed to read contents of file", "path", path, "file", file.Name)
68 }
69
70 if utf8.Valid(contents) {
71 readmeFileName = file.Name
72 readmeContents = string(contents)
73 break
74 }
75 }
76 }
77
78 // convert NiceTree -> tangled.RepoTempGetTree_TreeEntry
79 treeEntries := make([]*tangled.GitTempGetTree_TreeEntry, len(files))
80 for i, file := range files {
81 entry := &tangled.GitTempGetTree_TreeEntry{
82 Name: file.Name,
83 Mode: file.Mode,
84 Size: file.Size,
85 }
86 if file.LastCommit != nil {
87 entry.Last_commit = &tangled.GitTempGetTree_LastCommit{
88 Hash: file.LastCommit.Hash.String(),
89 Message: file.LastCommit.Message,
90 When: file.LastCommit.When.Format(time.RFC3339),
91 }
92 }
93 treeEntries[i] = entry
94 }
95
96 var parentPtr *string
97 if path != "" {
98 parentPtr = &path
99 }
100
101 var dotdotPtr *string
102 if path != "" {
103 dotdot := filepath.Dir(path)
104 if dotdot != "." {
105 dotdotPtr = &dotdot
106 }
107 }
108
109 return &tangled.GitTempGetTree_Output{
110 Ref: ref,
111 Parent: parentPtr,
112 Dotdot: dotdotPtr,
113 Files: treeEntries,
114 Readme: &tangled.GitTempGetTree_Readme{
115 Filename: readmeFileName,
116 Contents: readmeContents,
117 },
118 }, nil
119}