all repos

mugit @ 831d5c6

🐮 git server that your cow will love
6 files changed, 68 insertions(+), 57 deletions(-)
git: resolve name and paths
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 18:49:23 +0200
Change ID: nmqwpqklxkxxpytsrxvoxwsloswuktqs
Parent: dc62a68
A internal/git/paths.go

@@ -0,0 +1,20 @@

+package git + +import ( + "fmt" + "strings" + + securejoin "github.com/cyphar/filepath-securejoin" +) + +func ResolveName(name string) string { + return strings.TrimSuffix(name, ".git") + ".git" +} + +func ResolvePath(baseDir, repoName string) (string, error) { + path, err := securejoin.SecureJoin(baseDir, repoName) + if err != nil { + return "", fmt.Errorf("failed to secure join paths: %w", err) + } + return path, err +}
M internal/git/repo.go

@@ -20,6 +20,7 @@

var ( ErrEmptyRepo = errors.New("repository has no commits") ErrFileNotFound = errors.New("file not found") + ErrPrivate = errors.New("repository is private") ) type Repo struct {

@@ -29,7 +30,7 @@ h plumbing.Hash

} // Open opens a git repository at path. If ref is empty, HEAD is used. -func Open(path string, ref string) (*Repo, error) { +func Open(path, ref string) (*Repo, error) { var err error g := Repo{} g.path = path

@@ -55,6 +56,25 @@ }

g.h = *hash } return &g, nil +} + +// OpenPublic opens a repository, returns [ErrPrivate] if it's private. +func OpenPublic(path, ref string) (*Repo, error) { + r, err := Open(path, ref) + if err != nil { + return nil, err + } + + isPrivate, err := r.IsPrivate() + if err != nil { + return nil, err + } + + if isPrivate { + return nil, ErrPrivate + } + + return r, nil } func (g *Repo) IsEmpty() bool {
M internal/handlers/feed.go

@@ -7,8 +7,7 @@ "github.com/gorilla/feeds"

) func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return
M internal/handlers/git.go

@@ -6,7 +6,7 @@ "io"

"log/slog" "net/http" - securejoin "github.com/cyphar/filepath-securejoin" + "olexsmir.xyz/mugit/internal/git" "olexsmir.xyz/mugit/internal/git/gitx" )

@@ -83,18 +83,26 @@ }

} func (h *handlers) checkRepoPublicityAndGetPath(name string) (string, error) { - repoPath := repoNameToPath(name) - _, err := h.openPublicRepo(name, "") + name = git.ResolveName(name) + path, err := git.ResolvePath(h.c.Repo.Dir, name) if err != nil { return "", err } - path, err := securejoin.SecureJoin(h.c.Repo.Dir, repoPath) - if err != nil { + if _, err := git.OpenPublic(path, ""); err != nil { return "", err } - return path, nil + return path, err +} + +func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { + name = git.ResolveName(name) + path, err := git.ResolvePath(h.c.Repo.Dir, name) + if err != nil { + return nil, err + } + return git.OpenPublic(path, ref) } type flushWriter struct {
M internal/handlers/handlers.go

@@ -26,7 +26,7 @@

mux := http.NewServeMux() mux.HandleFunc("GET /", h.indexHandler) mux.HandleFunc("GET /index.xml", h.indexFeedHandler) - mux.HandleFunc("GET /static/{file}", h.serveStatic) + mux.HandleFunc("GET /static/{file}", h.serveStaticHandler) mux.HandleFunc("GET /{name}", h.multiplex) mux.HandleFunc("POST /{name}", h.multiplex) mux.HandleFunc("GET /{name}/{rest...}", h.multiplex)

@@ -43,14 +43,9 @@ handler := h.recoverMiddleware(mux)

return h.loggingMiddleware(handler) } -func (h *handlers) serveStatic(w http.ResponseWriter, r *http.Request) { +func (h *handlers) serveStaticHandler(w http.ResponseWriter, r *http.Request) { f := filepath.Clean(r.PathValue("file")) http.ServeFileFS(w, r, web.StaticFS, f) -} - -func repoNameToPath(name string) string { return name + ".git" } -func getNormalizedName(name string) string { - return strings.TrimSuffix(name, ".git") } var templateFuncs = template.FuncMap{
M internal/handlers/repo.go

@@ -15,7 +15,6 @@ "strconv"

"strings" "time" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/renderer/html"

@@ -43,8 +42,7 @@ extension.Linkify,

)) func (h *handlers) repoIndex(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return

@@ -57,7 +55,7 @@ return

} data := make(map[string]any) - data["name"] = name + data["name"] = repo.Name() data["desc"] = desc data["servername"] = h.c.Meta.Host data["meta"] = h.c.Meta

@@ -123,7 +121,7 @@ h.templ(w, "repo_index", data)

} func (h *handlers) repoTreeHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") treePath := r.PathValue("rest")

@@ -158,7 +156,7 @@ h.templ(w, "repo_tree", data)

} func (h *handlers) fileContentsHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") treePath := r.PathValue("rest")

@@ -222,7 +220,7 @@ h.templ(w, "repo_file", data)

} func (h *handlers) logHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") repo, err := h.openPublicRepo(name, ref)

@@ -254,7 +252,7 @@ h.templ(w, "repo_log", data)

} func (h *handlers) commitHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") repo, err := h.openPublicRepo(name, ref) if err != nil {

@@ -286,8 +284,7 @@ h.templ(w, "repo_commit", data)

} func (h *handlers) refsHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return

@@ -319,7 +316,7 @@ }

data := make(map[string]any) data["meta"] = h.c.Meta - data["name"] = name + data["name"] = repo.Name() data["desc"] = desc data["ref"] = masterBranch data["branches"] = branches

@@ -353,33 +350,6 @@ }

} } -var errPrivateRepo = errors.New("private err") - -func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { - // Convert normalized name back to filesystem path with .git suffix - name = repoNameToPath(name) - - path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) - if err != nil { - return nil, err - } - - repo, err := git.Open(path, ref) - if err != nil { - return nil, err - } - - isPrivate, err := repo.IsPrivate() - if err != nil { - return nil, err - } - if isPrivate { - return nil, errPrivateRepo - } - - return repo, nil -} - type repoList struct { Name string Desc string

@@ -400,8 +370,7 @@ continue

} name := dir.Name() - normalizedName := getNormalizedName(name) - repo, err := h.openPublicRepo(normalizedName, "") + repo, err := h.openPublicRepo(name, "") if err != nil { // if it's not git repo, just ignore it continue

@@ -420,7 +389,7 @@ continue

} repos = append(repos, repoList{ - Name: normalizedName, + Name: repo.Name(), Desc: desc, LastCommit: lastCommit.Committed, })