fierj
PublicTiny personal git forge
9128abf3c5f9ad6c134818535b000d905dfd0b4c
diff --git a/git.go b/git.go
index 3baab58..b452e08 100644
--- a/git.go
+++ b/git.go
@@ -3,6 +3,7 @@ package main
import (
"bytes"
"fmt"
+ "os"
"os/exec"
"path"
"path/filepath"
@@ -74,6 +75,23 @@ func InitRepo(cfg *Config, name string) error {
return nil
}
+func ListRepos(cfg Config) ([]*Git, error) {
+ entries, err := os.ReadDir(cfg.Dir)
+ if err != nil {
+ return nil, err
+ }
+ repos := []*Git{}
+ for _, e := range entries {
+ if !e.IsDir() || !strings.HasSuffix(e.Name(), ".git") {
+ continue
+ }
+ name := strings.TrimSuffix(e.Name(), ".git")
+ git := &Git{Dir: cfg.Dir, Name: name}
+ repos = append(repos, git)
+ }
+ return repos, nil
+}
+
func (g *Git) Repo() string { return g.Name }
func (g *Git) Description() string { return "" }
diff --git a/handlers.go b/handlers.go
index 11bb595..7d90030 100644
--- a/handlers.go
+++ b/handlers.go
@@ -15,6 +15,19 @@ func renderError(w http.ResponseWriter, tmpl *template.Template, code int, messa
})
}
+func Repos(cfg Config, tmpl *template.Template) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ repos, err := ListRepos(cfg)
+ if err != nil {
+ renderError(w, tmpl, http.StatusInternalServerError, "failed to list repositories: "+err.Error())
+ return
+ }
+ tmpl.ExecuteTemplate(w, "repos.html", map[string]any{
+ "Repos": repos,
+ })
+ }
+}
+
func Tree(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type FileEntry struct {
diff --git a/main.go b/main.go
index 73e7e16..f6615a8 100644
--- a/main.go
+++ b/main.go
@@ -86,6 +86,7 @@ func main() {
mux := http.NewServeMux()
+ mux.HandleFunc("GET /", Repos(cfg, tmpl))
mux.HandleFunc("GET /{repo}", Tree(cfg, tmpl))
mux.HandleFunc("GET /{repo}/tree/{ref}/{path...}", Tree(cfg, tmpl))
mux.HandleFunc("GET /{repo}/blob/{ref}/{path...}", Blob(cfg, tmpl))
diff --git a/templates/repos.html b/templates/repos.html
new file mode 100644
index 0000000..debe97e
--- /dev/null
+++ b/templates/repos.html
@@ -0,0 +1,29 @@
+{{template "head" .}}
+{{define "title"}}repositories - fierj{{end}}
+<h1 style="margin-bottom: var(--space-lg);">Repositories</h1>
+{{if .Repos}}
+<div style="display: grid; gap: var(--space-md);">
+ {{range .Repos}}
+ <div
+ style="border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); background: var(--bg);">
+ <div style="display: flex; align-items: center; gap: var(--space-sm);">
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="color: var(--text-secondary);">
+ <path
+ d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z" />
+ </svg>
+ <a href="/{{.Name}}" style="font-weight: 600; font-size: 1rem;">{{.Name}}</a>
+ <span
+ style="font-size: 0.7rem; border: 1px solid var(--border); border-radius: 2em; padding: 0.1rem 0.5rem; color: var(--text-secondary);">Public</span>
+ </div>
+ {{if .Description}}<p style="color: var(--text-secondary); margin-top: var(--space-xs); font-size: 0.85rem;">
+ {{.Description}}</p>{{end}}
+ </div>
+ {{end}}
+</div>
+{{else}}
+<div class="empty-state">
+ <p>No repositories yet. Create your first one.</p>
+</div>
+{{end}}
+{{template "foot" .}}
+