all repos

mugit @ deb6f00

🐮 git server that your cow will love
5 files changed, 126 insertions(+), 39 deletions(-)
show binary files
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-14 17:02:46 +0200
Change ID: sltryzxnkwwzuvxsqnnmposuynqytlrr
Parent: 2487773
M internal/git/repo.go

@@ -160,33 +160,6 @@

return newCommit(c), nil } -func (g *Repo) FileContent(path string) (string, error) { - c, err := g.r.CommitObject(g.h) - if err != nil { - return "", fmt.Errorf("commit object: %w", err) - } - - tree, err := c.Tree() - if err != nil { - return "", fmt.Errorf("file tree: %w", err) - } - - file, err := tree.File(path) - if err != nil { - if errors.Is(err, object.ErrFileNotFound) { - return "", ErrFileNotFound - } - return "", err - } - - isbin, _ := file.IsBinary() - if !isbin { - return file.Contents() - } else { - return "Not displaying binary file", nil - } -} - type Branch struct { Name string LastUpdate time.Time
M internal/git/tree.go

@@ -1,7 +1,12 @@

package git import ( + "errors" "fmt" + "io" + "mime" + "path/filepath" + "strings" "github.com/go-git/go-git/v5/plumbing/object" )

@@ -60,3 +65,69 @@ }

return files, nil } + +type FileContent struct { + IsBinary bool + Content []byte + Mime string + Size int64 +} + +func (fc FileContent) IsImage() bool { + return strings.HasPrefix(fc.Mime, "image/") +} + +func (fc *FileContent) String() string { + if fc.IsBinary { + return "" + } + return string(fc.Content) +} + +func (g *Repo) FileContent(path string) (*FileContent, error) { + c, err := g.r.CommitObject(g.h) + if err != nil { + return &FileContent{}, fmt.Errorf("commit object: %w", err) + } + + tree, err := c.Tree() + if err != nil { + return &FileContent{}, fmt.Errorf("file tree: %w", err) + } + + file, err := tree.File(path) + if err != nil { + if errors.Is(err, object.ErrFileNotFound) { + return &FileContent{}, ErrFileNotFound + } + return &FileContent{}, err + } + + reader, err := file.Reader() + if err != nil { + return nil, fmt.Errorf("file reader: %w", err) + } + defer reader.Close() + + content, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("read file: %w", err) + } + + isBin, _ := file.IsBinary() + mimeType := mime.TypeByExtension(filepath.Ext(path)) + if mimeType == "" { + if isBin { + mimeType = "application/octet-stream" + } else { + mimeType = "text/plain" + } + } + + return &FileContent{ + IsBinary: isBin, + Content: content, + Mime: mimeType, + Size: file.Size, + }, nil +}
M internal/handlers/repo.go

@@ -69,8 +69,17 @@ }

var readmeContents template.HTML for _, readme := range h.c.Repo.Readmes { + fc, ferr := repo.FileContent(readme) + if ferr != nil { + continue + } + + if fc.IsBinary { + continue + } + ext := filepath.Ext(readme) - content, _ := repo.FileContent(readme) + content := fc.String() if len(content) > 0 { switch ext { case ".md", ".markdown", ".mkd":

@@ -178,7 +187,7 @@ h.write500(w, err)

return } - contents, err := repo.FileContent(treePath) + fc, err := repo.FileContent(treePath) if err != nil { if errors.Is(err, git.ErrFileNotFound) { h.write404(w, err)

@@ -190,8 +199,23 @@ }

if raw { w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte(contents)) + w.Header().Set("Content-Type", fc.Mime) + w.Write(fc.Content) + return + } + + if fc.IsImage() || fc.IsBinary { + data := make(map[string]any) + data["name"] = name + data["ref"] = ref + data["desc"] = desc + data["path"] = treePath + data["is_image"] = fc.IsImage() + data["is_binary"] = fc.IsBinary + data["mime_type"] = fc.Mime + data["size"] = fc.Size + data["meta"] = h.c.Meta + h.templ(w, "repo_file", data) return }

@@ -201,7 +225,8 @@ data["ref"] = ref

data["desc"] = desc data["path"] = treePath - lc, err := countLines(strings.NewReader(contents)) + contentStr := fc.String() + lc, err := countLines(strings.NewReader(contentStr)) if err != nil { slog.Error("failed to count line numbers", "err", err) }

@@ -214,7 +239,7 @@ }

} data["linecount"] = lines - data["content"] = contents + data["content"] = contentStr data["meta"] = h.c.Meta h.templ(w, "repo_file", data)
M web/static/style.css

@@ -265,6 +265,11 @@ padding-left: 1.5ch;

overflow-y: hidden; overflow-x: auto; } +.image-viewer, .binary-viewer { padding: 1rem 0; } +.image-viewer img { + max-width: 100%; + height: auto; +} .line-numbers pre, .file-content pre {
M web/templates/repo_file.html

@@ -1,15 +1,27 @@

{{ define "repo_file" }} <html> <head> - {{ template "head" . }} + {{ template "head" . }} </head> {{ template "repo_header" . }} <body> <main> - <p>{{ .path }} (<a style="color: gray" href="?raw=true">view raw</a>)</p> + <p>{{ .path }} (<a class="muted" href="?raw=true">view raw</a>)</p> <div class="file-wrapper"> - <table> - <tbody><tr> + {{ if .is_image }} + <div class="image-viewer"> + <p>{{.mime_type}} • {{.size}} bytes</p> + <img src="?raw=true" alt="{{- .path -}}"> + </div> + {{ else if .is_binary }} + <div class="binary-viewer"> + <p>Binary file ({{.mime_type}})</p> + <p>Size: {{.size}} bytes</p> + <a class="link" href="?raw=true" download>[ Download ]</a> + </div> + {{ else }} + <table> + <tbody><tr> <td class="line-numbers"> <pre>{{- range .linecount }} <a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a>

@@ -18,8 +30,9 @@ </td>

<td class="file-content"> <pre>{{- .content -}}</pre> </td> - </tbody></tr> - </table> + </tbody></tr> + </table> + {{ end }} </div> </main> </body>