forked from tangled.org/core
this repo has no description

appview: implement a basic breadcrumb for files

This needs another pass to fix the intermediate directory links etc.

anirudh.fi f94a9007 0aa1229c

verified
Changed files
+48 -12
appview
pages
templates
repo
knotserver
types
+1
.gitignore
··· 1 .direnv/ 2 tmp 3 *.db 4 .bin/ 5 appview/pages/static/* 6 result
··· 1 .direnv/ 2 tmp 3 *.db 4 + *.db-* 5 .bin/ 6 appview/pages/static/* 7 result
appview.db-journal

This is a binary file and will not be displayed.

+10
appview/pages/pages.go
··· 8 "io/fs" 9 "log" 10 "net/http" 11 "path" 12 "strings" 13 14 "github.com/dustin/go-humanize" ··· 259 LoggedInUser *auth.User 260 RepoInfo RepoInfo 261 Active string 262 types.RepoBlobResponse 263 } 264 265 func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { 266 params.Active = "overview" 267 return p.executeRepo("repo/blob", w, params) 268 }
··· 8 "io/fs" 9 "log" 10 "net/http" 11 + "os" 12 "path" 13 + "path/filepath" 14 "strings" 15 16 "github.com/dustin/go-humanize" ··· 261 LoggedInUser *auth.User 262 RepoInfo RepoInfo 263 Active string 264 + File string 265 + PathElems []string 266 types.RepoBlobResponse 267 } 268 269 func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { 270 + path := filepath.Dir(params.Path) 271 + file := filepath.Base(params.Path) 272 + 273 + params.PathElems = strings.Split(path, string(os.PathSeparator)) 274 + params.Path = path 275 + params.File = file 276 params.Active = "overview" 277 return p.executeRepo("repo/blob", w, params) 278 }
+9 -1
appview/pages/templates/repo/blob.html
··· 3 {{ $tot_lines := len $lines }} 4 {{ $tot_chars := len (printf "%d" $tot_lines) }} 5 {{ $code_number_style := "text-gray-400 left-0 bg-white text-right mr-2 select-none" }} 6 <pre class="font-mono text-sm overflow-auto relative text-ellipsis"><code>{{ range $idx, $line := $lines }}<span class="flex"> 7 <span class="{{ $code_number_style }}" style="min-width: {{$tot_chars}}ch;">{{ add $idx 1 }}</span> 8 - <span class="whitespace-pre">{{ $line }}</span></span>{{ else }}<em class="text-gray-400">this file is empty</em>{{ end }}</code></pre> 9 {{ end }}
··· 3 {{ $tot_lines := len $lines }} 4 {{ $tot_chars := len (printf "%d" $tot_lines) }} 5 {{ $code_number_style := "text-gray-400 left-0 bg-white text-right mr-2 select-none" }} 6 + <div class="pb-2 text-lg"> 7 + {{ range .PathElems }} 8 + <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ . }}" class="text-bold text-gray-500">{{ . }}</a> / 9 + {{ end }}<span class="">{{ .File }}</span> 10 + </div> 11 + 12 + 13 + {{ if .IsBinary }}<p class="text-center text-gray-400">This is a binary file and will not be displayed.</p>{{ else }} 14 <pre class="font-mono text-sm overflow-auto relative text-ellipsis"><code>{{ range $idx, $line := $lines }}<span class="flex"> 15 <span class="{{ $code_number_style }}" style="min-width: {{$tot_chars}}ch;">{{ add $idx 1 }}</span> 16 + <span class="whitespace-pre">{{ $line }}</span></span>{{ else }}<em class="text-gray-400">this file is empty</em>{{ end }}</code></pre>{{ end}} 17 {{ end }}
+5 -1
knotserver/git/git.go
··· 14 "github.com/go-git/go-git/v5/plumbing/object" 15 ) 16 17 type GitRepo struct { 18 r *git.Repository 19 h plumbing.Hash ··· 148 if !isbin { 149 return file.Contents() 150 } else { 151 - return "Not displaying binary file", nil 152 } 153 } 154
··· 14 "github.com/go-git/go-git/v5/plumbing/object" 15 ) 16 17 + var ( 18 + ErrBinaryFile = fmt.Errorf("binary file") 19 + ) 20 + 21 type GitRepo struct { 22 r *git.Repository 23 h plumbing.Hash ··· 152 if !isbin { 153 return file.Contents() 154 } else { 155 + return "", ErrBinaryFile 156 } 157 } 158
+1
knotserver/handler.go
··· 76 r.Post("/git-upload-pack", h.UploadPack) 77 78 r.Route("/tree/{ref}", func(r chi.Router) { 79 r.Get("/*", h.RepoTree) 80 }) 81
··· 76 r.Post("/git-upload-pack", h.UploadPack) 77 78 r.Route("/tree/{ref}", func(r chi.Router) { 79 + r.Get("/", h.RepoIndex) 80 r.Get("/*", h.RepoTree) 81 }) 82
+21 -10
knotserver/routes.go
··· 32 func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 33 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 34 l := h.l.With("path", path, "handler", "RepoIndex") 35 36 - gr, err := git.Open(path, "") 37 if err != nil { 38 if errors.Is(err, plumbing.ErrReferenceNotFound) { 39 resp := types.RepoIndexResponse{ ··· 84 l.Warn("no readme found") 85 } 86 87 - mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch) 88 if err != nil { 89 writeError(w, err.Error(), http.StatusInternalServerError) 90 - l.Error("finding main branch", "error", err.Error()) 91 return 92 } 93 94 - files, err := gr.FileTree("") 95 - if err != nil { 96 - writeError(w, err.Error(), http.StatusInternalServerError) 97 - l.Error("file tree", "error", err.Error()) 98 - return 99 } 100 101 resp := types.RepoIndexResponse{ 102 IsEmpty: false, 103 - Ref: mainBranch, 104 Commits: commits, 105 Description: getDescription(path), 106 Readme: readmeContent, ··· 156 return 157 } 158 159 contents, err := gr.FileContent(treePath) 160 - if err != nil { 161 writeError(w, err.Error(), http.StatusInternalServerError) 162 return 163 } ··· 168 Ref: ref, 169 Contents: string(safe), 170 Path: treePath, 171 } 172 173 h.showFile(resp, w, l)
··· 32 func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 33 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 34 l := h.l.With("path", path, "handler", "RepoIndex") 35 + ref := chi.URLParam(r, "ref") 36 37 + gr, err := git.Open(path, ref) 38 if err != nil { 39 if errors.Is(err, plumbing.ErrReferenceNotFound) { 40 resp := types.RepoIndexResponse{ ··· 85 l.Warn("no readme found") 86 } 87 88 + files, err := gr.FileTree("") 89 if err != nil { 90 writeError(w, err.Error(), http.StatusInternalServerError) 91 + l.Error("file tree", "error", err.Error()) 92 return 93 } 94 95 + if ref == "" { 96 + mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch) 97 + if err != nil { 98 + writeError(w, err.Error(), http.StatusInternalServerError) 99 + l.Error("finding main branch", "error", err.Error()) 100 + return 101 + } 102 + ref = mainBranch 103 } 104 105 resp := types.RepoIndexResponse{ 106 IsEmpty: false, 107 + Ref: ref, 108 Commits: commits, 109 Description: getDescription(path), 110 Readme: readmeContent, ··· 160 return 161 } 162 163 + var isBinaryFile bool = false 164 contents, err := gr.FileContent(treePath) 165 + if errors.Is(err, git.ErrBinaryFile) { 166 + isBinaryFile = true 167 + } else if errors.Is(err, object.ErrFileNotFound) { 168 + notFound(w) 169 + return 170 + } else if err != nil { 171 writeError(w, err.Error(), http.StatusInternalServerError) 172 return 173 } ··· 178 Ref: ref, 179 Contents: string(safe), 180 Path: treePath, 181 + IsBinary: isBinaryFile, 182 } 183 184 h.showFile(resp, w, l)
+1
types/repo.go
··· 65 Contents string `json:"contents,omitempty"` 66 Ref string `json:"ref,omitempty"` 67 Path string `json:"path,omitempty"` 68 69 Lines int `json:"lines,omitempty"` 70 }
··· 65 Contents string `json:"contents,omitempty"` 66 Ref string `json:"ref,omitempty"` 67 Path string `json:"path,omitempty"` 68 + IsBinary bool `json:"is_binary,omitempty"` 69 70 Lines int `json:"lines,omitempty"` 71 }