This repository has no description
0

Configure Feed

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

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