5 files changed,
126 insertions(+),
39 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-02-14 17:02:46 +0200
Authored at:
2026-02-14 16:02:09 +0200
Change ID:
sltryzxnkwwzuvxsqnnmposuynqytlrr
Parent:
2487773
M
internal/git/repo.go
··· 160 160 return newCommit(c), nil 161 161 } 162 162 163 -func (g *Repo) FileContent(path string) (string, error) { 164 - c, err := g.r.CommitObject(g.h) 165 - if err != nil { 166 - return "", fmt.Errorf("commit object: %w", err) 167 - } 168 - 169 - tree, err := c.Tree() 170 - if err != nil { 171 - return "", fmt.Errorf("file tree: %w", err) 172 - } 173 - 174 - file, err := tree.File(path) 175 - if err != nil { 176 - if errors.Is(err, object.ErrFileNotFound) { 177 - return "", ErrFileNotFound 178 - } 179 - return "", err 180 - } 181 - 182 - isbin, _ := file.IsBinary() 183 - if !isbin { 184 - return file.Contents() 185 - } else { 186 - return "Not displaying binary file", nil 187 - } 188 -} 189 - 190 163 type Branch struct { 191 164 Name string 192 165 LastUpdate time.Time
M
internal/git/tree.go
··· 1 1 package git 2 2 3 3 import ( 4 + "errors" 4 5 "fmt" 6 + "io" 7 + "mime" 8 + "path/filepath" 9 + "strings" 5 10 6 11 "github.com/go-git/go-git/v5/plumbing/object" 7 12 ) ··· 60 65 61 66 return files, nil 62 67 } 68 + 69 +type FileContent struct { 70 + IsBinary bool 71 + Content []byte 72 + Mime string 73 + Size int64 74 +} 75 + 76 +func (fc FileContent) IsImage() bool { 77 + return strings.HasPrefix(fc.Mime, "image/") 78 +} 79 + 80 +func (fc *FileContent) String() string { 81 + if fc.IsBinary { 82 + return "" 83 + } 84 + return string(fc.Content) 85 +} 86 + 87 +func (g *Repo) FileContent(path string) (*FileContent, error) { 88 + c, err := g.r.CommitObject(g.h) 89 + if err != nil { 90 + return &FileContent{}, fmt.Errorf("commit object: %w", err) 91 + } 92 + 93 + tree, err := c.Tree() 94 + if err != nil { 95 + return &FileContent{}, fmt.Errorf("file tree: %w", err) 96 + } 97 + 98 + file, err := tree.File(path) 99 + if err != nil { 100 + if errors.Is(err, object.ErrFileNotFound) { 101 + return &FileContent{}, ErrFileNotFound 102 + } 103 + return &FileContent{}, err 104 + } 105 + 106 + reader, err := file.Reader() 107 + if err != nil { 108 + return nil, fmt.Errorf("file reader: %w", err) 109 + } 110 + defer reader.Close() 111 + 112 + content, err := io.ReadAll(reader) 113 + if err != nil { 114 + return nil, fmt.Errorf("read file: %w", err) 115 + } 116 + 117 + isBin, _ := file.IsBinary() 118 + mimeType := mime.TypeByExtension(filepath.Ext(path)) 119 + if mimeType == "" { 120 + if isBin { 121 + mimeType = "application/octet-stream" 122 + } else { 123 + mimeType = "text/plain" 124 + } 125 + } 126 + 127 + return &FileContent{ 128 + IsBinary: isBin, 129 + Content: content, 130 + Mime: mimeType, 131 + Size: file.Size, 132 + }, nil 133 +}
M
internal/handlers/repo.go
··· 69 69 70 70 var readmeContents template.HTML 71 71 for _, readme := range h.c.Repo.Readmes { 72 + fc, ferr := repo.FileContent(readme) 73 + if ferr != nil { 74 + continue 75 + } 76 + 77 + if fc.IsBinary { 78 + continue 79 + } 80 + 72 81 ext := filepath.Ext(readme) 73 - content, _ := repo.FileContent(readme) 82 + content := fc.String() 74 83 if len(content) > 0 { 75 84 switch ext { 76 85 case ".md", ".markdown", ".mkd": ··· 178 187 return 179 188 } 180 189 181 - contents, err := repo.FileContent(treePath) 190 + fc, err := repo.FileContent(treePath) 182 191 if err != nil { 183 192 if errors.Is(err, git.ErrFileNotFound) { 184 193 h.write404(w, err) ··· 190 199 191 200 if raw { 192 201 w.WriteHeader(http.StatusOK) 193 - w.Header().Set("Content-Type", "text/plain") 194 - w.Write([]byte(contents)) 202 + w.Header().Set("Content-Type", fc.Mime) 203 + w.Write(fc.Content) 204 + return 205 + } 206 + 207 + if fc.IsImage() || fc.IsBinary { 208 + data := make(map[string]any) 209 + data["name"] = name 210 + data["ref"] = ref 211 + data["desc"] = desc 212 + data["path"] = treePath 213 + data["is_image"] = fc.IsImage() 214 + data["is_binary"] = fc.IsBinary 215 + data["mime_type"] = fc.Mime 216 + data["size"] = fc.Size 217 + data["meta"] = h.c.Meta 218 + h.templ(w, "repo_file", data) 195 219 return 196 220 } 197 221 ··· 201 225 data["desc"] = desc 202 226 data["path"] = treePath 203 227 204 - lc, err := countLines(strings.NewReader(contents)) 228 + contentStr := fc.String() 229 + lc, err := countLines(strings.NewReader(contentStr)) 205 230 if err != nil { 206 231 slog.Error("failed to count line numbers", "err", err) 207 232 } ··· 214 239 } 215 240 216 241 data["linecount"] = lines 217 - data["content"] = contents 242 + data["content"] = contentStr 218 243 data["meta"] = h.c.Meta 219 244 220 245 h.templ(w, "repo_file", data)
M
web/templates/repo_file.html
··· 1 1 {{ define "repo_file" }} 2 2 <html> 3 3 <head> 4 - {{ template "head" . }} 4 + {{ template "head" . }} 5 5 </head> 6 6 {{ template "repo_header" . }} 7 7 <body> 8 8 <main> 9 - <p>{{ .path }} (<a style="color: gray" href="?raw=true">view raw</a>)</p> 9 + <p>{{ .path }} (<a class="muted" href="?raw=true">view raw</a>)</p> 10 10 <div class="file-wrapper"> 11 - <table> 12 - <tbody><tr> 11 + {{ if .is_image }} 12 + <div class="image-viewer"> 13 + <p>{{.mime_type}} • {{.size}} bytes</p> 14 + <img src="?raw=true" alt="{{- .path -}}"> 15 + </div> 16 + {{ else if .is_binary }} 17 + <div class="binary-viewer"> 18 + <p>Binary file ({{.mime_type}})</p> 19 + <p>Size: {{.size}} bytes</p> 20 + <a class="link" href="?raw=true" download>[ Download ]</a> 21 + </div> 22 + {{ else }} 23 + <table> 24 + <tbody><tr> 13 25 <td class="line-numbers"> 14 26 <pre>{{- range .linecount }} 15 27 <a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a> ··· 18 30 <td class="file-content"> 19 31 <pre>{{- .content -}}</pre> 20 32 </td> 21 - </tbody></tr> 22 - </table> 33 + </tbody></tr> 34 + </table> 35 + {{ end }} 23 36 </div> 24 37 </main> 25 38 </body>