+ New

fierj

Public

Tiny 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" .}}
+