This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knotmirror / xrpc / git_get_tree.go
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}