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