Monorepo for Tangled
at push-pzoouymnzvnq 146 lines 3.4 kB view raw
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 "tangled.org/core/types" 13 xrpcerr "tangled.org/core/xrpc/errors" 14) 15 16func (x *Xrpc) RepoTree(w http.ResponseWriter, r *http.Request) { 17 ctx := r.Context() 18 19 repo := r.URL.Query().Get("repo") 20 repoPath, err := x.parseRepoParam(repo) 21 if err != nil { 22 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 23 return 24 } 25 26 ref := r.URL.Query().Get("ref") 27 // ref can be empty (git.Open handles this) 28 29 path := r.URL.Query().Get("path") 30 // path can be empty (defaults to root) 31 32 gr, err := git.Open(repoPath, ref) 33 if err != nil { 34 x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref) 35 writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 36 return 37 } 38 39 files, err := gr.FileTree(ctx, path) 40 if err != nil { 41 x.Logger.Error("failed to get file tree", "error", err, "path", path) 42 writeError(w, xrpcerr.NewXrpcError( 43 xrpcerr.WithTag("PathNotFound"), 44 xrpcerr.WithMessage("failed to read repository tree"), 45 ), http.StatusNotFound) 46 return 47 } 48 49 // if any of these files are a readme candidate, pass along its blob contents too 50 var readmeFileName string 51 var readmeContents string 52 for _, file := range files { 53 if markup.IsReadmeFile(file.Name) { 54 contents, err := gr.RawContent(filepath.Join(path, file.Name)) 55 if err != nil { 56 x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name) 57 } 58 59 if utf8.Valid(contents) { 60 readmeFileName = file.Name 61 readmeContents = string(contents) 62 break 63 } 64 } 65 } 66 67 // convert NiceTree -> tangled.RepoTree_TreeEntry 68 treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files)) 69 for i, file := range files { 70 entry := &tangled.RepoTree_TreeEntry{ 71 Name: file.Name, 72 Mode: file.Mode, 73 Size: file.Size, 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(time.RFC3339), 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 Readme: &tangled.RepoTree_Readme{ 106 Filename: readmeFileName, 107 Contents: readmeContents, 108 }, 109 } 110 111 // calculate lastCommit for the directory as a whole 112 var lastCommitTree *types.LastCommitInfo 113 for _, e := range files { 114 if e.LastCommit == nil { 115 continue 116 } 117 118 if lastCommitTree == nil { 119 lastCommitTree = e.LastCommit 120 continue 121 } 122 123 if lastCommitTree.When.After(e.LastCommit.When) { 124 lastCommitTree = e.LastCommit 125 } 126 } 127 128 if lastCommitTree != nil { 129 response.LastCommit = &tangled.RepoTree_LastCommit{ 130 Hash: lastCommitTree.Hash.String(), 131 Message: lastCommitTree.Message, 132 When: lastCommitTree.When.Format(time.RFC3339), 133 } 134 135 // try to get author information 136 commit, err := gr.Commit(lastCommitTree.Hash) 137 if err == nil { 138 response.LastCommit.Author = &tangled.RepoTree_Signature{ 139 Name: commit.Author.Name, 140 Email: commit.Author.Email, 141 } 142 } 143 } 144 145 writeJson(w, response) 146}