Monorepo for Tangled tangled.org
4

Configure Feed

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

1package repo 2 3import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "path" 8 "strings" 9 "time" 10 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/db" 13 "tangled.org/core/appview/pages" 14 "tangled.org/core/appview/pages/markup" 15 "tangled.org/core/appview/reporesolver" 16 xrpcclient "tangled.org/core/appview/xrpcclient" 17 "tangled.org/core/types" 18 19 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 20 "github.com/go-chi/chi/v5" 21 "github.com/go-git/go-git/v5/plumbing" 22) 23 24func (rp *Repo) Tree(w http.ResponseWriter, r *http.Request) { 25 l := rp.logger.With("handler", "RepoTree") 26 f, err := rp.repoResolver.Resolve(r) 27 if err != nil { 28 l.Error("failed to fully resolve repo", "err", err) 29 return 30 } 31 ref := chi.URLParam(r, "ref") 32 ref, _ = url.PathUnescape(ref) 33 // if the tree path has a trailing slash, let's strip it 34 // so we don't 404 35 treePath := chi.URLParam(r, "*") 36 treePath, _ = url.PathUnescape(treePath) 37 treePath = strings.TrimSuffix(treePath, "/") 38 39 xrpcc := &indigoxrpc.Client{Host: rp.config.KnotMirror.Url} 40 xrpcResp, err := tangled.GitTempGetTree(r.Context(), xrpcc, treePath, ref, f.RepoDid) 41 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 42 l.Error("failed to call XRPC repo.tree", "xrpcerr", xrpcerr, "err", err) 43 rp.pages.Error503(w) 44 return 45 } 46 47 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 48 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, 49 // so we can safely redirect to the "parent" (which is the same file). 50 if len(xrpcResp.Files) == 0 && xrpcResp.Parent != nil && *xrpcResp.Parent == treePath { 51 redirectTo := fmt.Sprintf("/%s/blob/%s/%s", ownerSlashRepo, url.PathEscape(ref), *xrpcResp.Parent) 52 http.Redirect(w, r, redirectTo, http.StatusFound) 53 return 54 } 55 56 var readmeFile *tangled.GitTempGetTree_TreeEntry 57 // Convert XRPC response to internal types.RepoTreeResponse 58 files := make([]types.NiceTree, len(xrpcResp.Files)) 59 for i, xrpcFile := range xrpcResp.Files { 60 file := types.NiceTree{ 61 Name: xrpcFile.Name, 62 Mode: xrpcFile.Mode, 63 Size: int64(xrpcFile.Size), 64 } 65 // Convert last commit info if present 66 if xrpcFile.Last_commit != nil { 67 commitWhen, _ := time.Parse(time.RFC3339, xrpcFile.Last_commit.When) 68 file.LastCommit = &types.LastCommitInfo{ 69 Hash: plumbing.NewHash(xrpcFile.Last_commit.Hash), 70 Message: xrpcFile.Last_commit.Message, 71 When: commitWhen, 72 } 73 } 74 files[i] = file 75 if markup.IsReadmeFile(xrpcFile.Name, xrpcFile.Mode) { 76 readmeFile = xrpcFile 77 } 78 } 79 sortFiles(files) 80 81 var ( 82 readmeFileName string 83 readmeFileContent string 84 ) 85 if readmeFile != nil { 86 bytes, err := tangled.GitTempGetBlob(r.Context(), xrpcc, path.Join(treePath, readmeFile.Name), ref, f.RepoDid) 87 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 88 l.Error("failed to call XRPC git.getBlob", "xrpcerr", xrpcerr, "err", err) 89 rp.pages.Error503(w) 90 return 91 } 92 readmeFileName = readmeFile.Name 93 readmeFileContent = string(bytes) 94 } 95 var breadcrumbs [][]string 96 breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", ownerSlashRepo, url.PathEscape(ref))}) 97 if treePath != "" { 98 for idx, elem := range strings.Split(treePath, "/") { 99 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))}) 100 } 101 } 102 103 // Get email to DID mapping for commit author 104 var emails []string 105 if xrpcResp.LastCommit != nil && xrpcResp.LastCommit.Author != nil { 106 emails = append(emails, xrpcResp.LastCommit.Author.Email) 107 } 108 emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true) 109 if err != nil { 110 l.Error("failed to get email to did mapping", "err", err) 111 emailToDidMap = make(map[string]string) 112 } 113 114 var lastCommitInfo *types.LastCommitInfo 115 if xrpcResp.LastCommit != nil { 116 when, _ := time.Parse(time.RFC3339, xrpcResp.LastCommit.When) 117 lastCommitInfo = &types.LastCommitInfo{ 118 Hash: plumbing.NewHash(xrpcResp.LastCommit.Hash), 119 Message: xrpcResp.LastCommit.Message, 120 When: when, 121 } 122 if xrpcResp.LastCommit.Author != nil { 123 lastCommitInfo.Author.Name = xrpcResp.LastCommit.Author.Name 124 lastCommitInfo.Author.Email = xrpcResp.LastCommit.Author.Email 125 lastCommitInfo.Author.When, _ = time.Parse(time.RFC3339, xrpcResp.LastCommit.Author.When) 126 } 127 } 128 129 user := rp.oauth.GetMultiAccountUser(r) 130 rp.pages.RepoTree(w, pages.RepoTreeParams{ 131 BaseParams: pages.BaseParamsFromContext(r.Context()), 132 BreadCrumbs: breadcrumbs, 133 Path: treePath, 134 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 135 EmailToDid: emailToDidMap, 136 LastCommitInfo: lastCommitInfo, 137 Ref: xrpcResp.Ref, 138 Parent: derefString(xrpcResp.Parent), 139 DotDot: derefString(xrpcResp.Dotdot), 140 Files: files, 141 ReadmeFileName: readmeFileName, 142 Readme: readmeFileContent, 143 }) 144} 145 146func derefString(s *string) string { 147 if s == nil { 148 return "" 149 } 150 return *s 151}