Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Build out the forge web UI

f09ef39efe44de693748053a7b596ef328fcf06b · Keenan Nemetz · 15d ago · parent 70b70264

Build out the forge web UI

Turn the web surface into a compact, server-rendered forge with HTMX as the
progressive dynamic layer.

- Split templates and assets into embedded files; add a tokenised dark theme,
  Lucide icons, and vendored HTMX.
- Add the repository Overview page and a separate chronological Activity feed
  with type filters and HTMX fragment updates.
- Finish the issue and change workflows: create pages, comments, close/reopen,
  labels and assignees, approval, discussion, line-anchored review comments,
  and HTMX list filters.
- Add syntax-highlighted blobs, contributor/statistics derivation, branch-aware
  code browsing, and a repository Runs list.
- Add a loopback-only development login for local write testing, with explicit
  configuration and listener checks.
- Extend tests for the new pages, fragments, labels, assignees, anchors, and
  development login.

core/git/stats_test.go +67

@@ -0,0 +1,67 @@
1 package git
2
3 import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "strings"
8 "testing"
9 )
10
11 // contributorsRepo writes two commits by one author and one by another, so the
12 // order and the identity parsing are both exercised.
13 func contributorsRepo(t *testing.T) string {
14 t.Helper()
15 dir := t.TempDir()
16 run := func(env []string, args ...string) {
17 t.Helper()
18 cmd := exec.Command("git", args...)
19 cmd.Dir = dir
20 cmd.Env = append(os.Environ(),
21 "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
22 "GIT_AUTHOR_NAME=vole", "GIT_AUTHOR_EMAIL=vole@example.com",
23 "GIT_COMMITTER_NAME=vole", "GIT_COMMITTER_EMAIL=vole@example.com")
24 cmd.Env = append(cmd.Env, env...)
25 if out, err := cmd.CombinedOutput(); err != nil {
26 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, out)
27 }
28 }
29 run(nil, "init", "-q", "-b", "main")
30 commit := func(file, who, email string) {
31 t.Helper()
32 if err := os.WriteFile(filepath.Join(dir, file), []byte(file+"\n"), 0o644); err != nil {
33 t.Fatal(err)
34 }
35 run(nil, "add", "-A")
36 run([]string{"GIT_AUTHOR_NAME=" + who, "GIT_AUTHOR_EMAIL=" + email}, "commit", "-qm", who)
37 }
38 commit("a.txt", "Ada", "ada@example.com")
39 commit("b.txt", "Ada", "ada@example.com")
40 commit("c.txt", "Grace", "grace@example.com")
41 return dir
42 }
43
44 func TestContributorsCountsAuthors(t *testing.T) {
45 r := open(t, contributorsRepo(t))
46 got, err := r.Contributors("main")
47 if err != nil {
48 t.Fatalf("Contributors: %v", err)
49 }
50 if len(got) != 2 {
51 t.Fatalf("contributors = %+v, want two", got)
52 }
53 if got[0].Name != "Ada" || got[0].Commits != 2 || got[0].Email != "ada@example.com" {
54 t.Fatalf("first contributor = %+v", got[0])
55 }
56 if got[1].Name != "Grace" || got[1].Commits != 1 {
57 t.Fatalf("second contributor = %+v", got[1])
58 }
59 }
60
61 func TestContributorsEmptyRevision(t *testing.T) {
62 r := open(t, contributorsRepo(t))
63 got, err := r.Contributors("")
64 if err != nil || got != nil {
65 t.Fatalf("empty revision = %+v, %v", got, err)
66 }
67 }