A vibe coded tangled fork which supports pijul.
at master 157 lines 3.8 kB view raw
1package xrpc 2 3import ( 4 "net/http" 5 "path/filepath" 6 "time" 7 "unicode/utf8" 8 9 "github.com/go-git/go-git/v5/plumbing" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/appview/pages/markup" 12 "tangled.org/core/knotserver/vcs" 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 // For pijul, also accept "channel" param and fall back to default 28 if channel := r.URL.Query().Get("channel"); channel != "" { 29 ref = channel 30 } 31 32 path := r.URL.Query().Get("path") 33 34 rv, err := vcs.Open(repoPath, ref) 35 if err != nil { 36 x.Logger.Error("failed to open repository", "error", err, "path", repoPath, "ref", ref) 37 writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 38 return 39 } 40 41 files, err := rv.FileTree(ctx, path) 42 if err != nil { 43 x.Logger.Error("failed to get file tree", "error", err, "path", path) 44 writeError(w, xrpcerr.NewXrpcError( 45 xrpcerr.WithTag("PathNotFound"), 46 xrpcerr.WithMessage("failed to read repository tree"), 47 ), http.StatusNotFound) 48 return 49 } 50 51 // if any of these files are a readme candidate, pass along its blob contents too 52 var readmeFileName string 53 var readmeContents string 54 for _, file := range files { 55 if markup.IsReadmeFile(file.Name) { 56 contents, err := rv.RawContent(filepath.Join(path, file.Name)) 57 if err != nil { 58 x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name) 59 continue 60 } 61 62 if utf8.Valid(contents) { 63 readmeFileName = file.Name 64 readmeContents = string(contents) 65 break 66 } 67 } 68 } 69 70 // convert vcs.TreeEntry -> tangled.RepoTree_TreeEntry 71 treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files)) 72 for i, file := range files { 73 entry := &tangled.RepoTree_TreeEntry{ 74 Name: file.Name, 75 Mode: file.Mode, 76 Size: file.Size, 77 } 78 79 if file.LastCommit != nil { 80 entry.Last_commit = &tangled.RepoTree_LastCommit{ 81 Hash: file.LastCommit.Hash, 82 Message: file.LastCommit.Message, 83 When: file.LastCommit.When.Format(time.RFC3339), 84 } 85 } 86 87 treeEntries[i] = entry 88 } 89 90 var parentPtr *string 91 if path != "" { 92 parentPtr = &path 93 } 94 95 var dotdotPtr *string 96 if path != "" { 97 dotdot := filepath.Dir(path) 98 if dotdot != "." { 99 dotdotPtr = &dotdot 100 } 101 } 102 103 response := tangled.RepoTree_Output{ 104 Ref: ref, 105 Parent: parentPtr, 106 Dotdot: dotdotPtr, 107 Files: treeEntries, 108 Readme: &tangled.RepoTree_Readme{ 109 Filename: readmeFileName, 110 Contents: readmeContents, 111 }, 112 } 113 114 // calculate lastCommit for the directory as a whole 115 var lastCommitTree *vcs.LastCommitInfo 116 for i := range files { 117 e := &files[i] 118 if e.LastCommit == nil { 119 continue 120 } 121 122 if lastCommitTree == nil { 123 lastCommitTree = e.LastCommit 124 continue 125 } 126 127 if lastCommitTree.When.Before(e.LastCommit.When) { 128 lastCommitTree = e.LastCommit 129 } 130 } 131 132 if lastCommitTree != nil { 133 response.LastCommit = &tangled.RepoTree_LastCommit{ 134 Hash: lastCommitTree.Hash, 135 Message: lastCommitTree.Message, 136 When: lastCommitTree.When.Format(time.RFC3339), 137 } 138 139 if lastCommitTree.Author != nil { 140 response.LastCommit.Author = &tangled.RepoTree_Signature{ 141 Name: lastCommitTree.Author.Name, 142 Email: lastCommitTree.Author.Email, 143 } 144 } else if gr := vcs.AsGit(rv); gr != nil { 145 // try to get author information from the git commit 146 commit, err := gr.Commit(plumbing.NewHash(lastCommitTree.Hash)) 147 if err == nil { 148 response.LastCommit.Author = &tangled.RepoTree_Signature{ 149 Name: commit.Author.Name, 150 Email: commit.Author.Email, 151 } 152 } 153 } 154 } 155 156 writeJson(w, response) 157}