scripts/
static/
templates/
.gitignore
30 B
.gitsigners
112 B
AGENTS.md
7.5 KiB
LICENSE
89 B
README.md
1.9 KiB
deploy
723 B
discuss.go
16.7 KiB
git.go
3.5 KiB
git_cli.go
16.0 KiB
git_http.go
1.9 KiB
go.mod
572 B
go.sum
1.9 KiB
handler.go
11.3 KiB
handler_test.go
69.0 KiB
main.go
5.2 KiB
template.go
8.9 KiB
watch
272 B
AGENTS.md
raw
| 1 | # Radiant Forge |
| 2 | |
| 3 | A self-hosted Git repository browser. Single Go binary, static HTML, no JavaScript. |
| 4 | Uses the `git` CLI for all Git operations. All assets (CSS, fonts, JS, SVG logo, |
| 5 | templates) are embedded via `//go:embed`. |
| 6 | |
| 7 | ## Build & Run |
| 8 | |
| 9 | go build . |
| 10 | ./forge -scan-path /srv/git -listen :8080 |
| 11 | |
| 12 | Flags: `-listen`, `-scan-path`, `-title`, `-base-url`, `-non-bare`, |
| 13 | `-username`, `-password`. |
| 14 | |
| 15 | ## Project Layout |
| 16 | |
| 17 | ``` |
| 18 | main.go Server init, repo scanning, HTTP mux, basic auth |
| 19 | handler.go All HTTP handlers + route dispatcher |
| 20 | git.go Types, diff collapsing, MIME map, tree sorting |
| 21 | git_cli.go Git CLI backend (all git operations shell out to `git`) |
| 22 | template.go Template loading, embed directives, template FuncMap |
| 23 | go.mod Module: forge |
| 24 | static/ |
| 25 | style.css Single stylesheet, embedded at compile time |
| 26 | radiant.svg Logo, embedded |
| 27 | fonts/ 5 TTF files (RethinkSans, IBM Plex Mono), embedded |
| 28 | js/ Syntax highlighting (hirad.js, hiril.js), embedded |
| 29 | templates/ |
| 30 | layout.html Base layout (header, nav, footer, wraps all pages) |
| 31 | index.html Repository list page |
| 32 | home.html Repo home: branch selector + file tree + content viewer |
| 33 | log.html Paginated commit log |
| 34 | commit.html Commit detail with unified diff |
| 35 | refs.html Branches and tags tables |
| 36 | error.html Error page |
| 37 | ``` |
| 38 | |
| 39 | ## Architecture |
| 40 | |
| 41 | ### Server struct (`main.go`) |
| 42 | |
| 43 | `server` holds: `repos map[string]*RepoInfo`, `sorted []string` (by last update), |
| 44 | `tmpl *templateSet`, `title`, `baseURL`, `scanPath`, `username`, `password`. |
| 45 | |
| 46 | Startup: parse flags -> `scanRepositories()` -> `loadTemplates()` -> `http.ListenAndServe`. |
| 47 | |
| 48 | Repository scanning checks top-level dirs in `scan-path` for bare repos |
| 49 | (`HEAD` + `objects/` + `refs/`) or non-bare repos (`.git` subdir, opt-in via `-non-bare`). |
| 50 | Repos are private by default; they are only served if a `public` file exists in the |
| 51 | git directory. Reads optional `description` and `owner` files from the git directory. |
| 52 | |
| 53 | Optional HTTP basic auth via `-username` and `-password` (both must be set together). |
| 54 | |
| 55 | ### Routing (`handler.go`) |
| 56 | |
| 57 | `route()` is the main dispatcher. URL structure: |
| 58 | |
| 59 | ``` |
| 60 | / -> handleIndex (repo list) |
| 61 | /:repo/ -> handleSummary (repo home) |
| 62 | /:repo/refs -> handleRefs (branches + tags) |
| 63 | /:repo/log/:ref?page=N -> handleLog (paginated commit log) |
| 64 | /:repo/tree/:ref/path... -> handleTree (file/dir browser) |
| 65 | /:repo/commit/:hash -> handleCommit (commit detail + diff) |
| 66 | /:repo/raw/:ref/path... -> handleRaw (raw file download) |
| 67 | /style.css -> serveCSS |
| 68 | /radiant.svg -> serveLogo |
| 69 | /js/* -> serveJS |
| 70 | /fonts/* -> serveFont |
| 71 | ``` |
| 72 | |
| 73 | Static assets are registered on the mux directly; everything else goes through `route()`. |
| 74 | |
| 75 | ### Template data flow |
| 76 | |
| 77 | All pages receive a `pageData` struct: |
| 78 | - `SiteTitle`, `BaseURL`, `Repo`, `Description`, `Section`, `Ref`, `CommitHash` |
| 79 | - `Data any` — page-specific data struct (e.g. `homeData`, `logData`, etc.) |
| 80 | |
| 81 | Each page template defines `{{define "content"}}` which `layout.html` renders via |
| 82 | `{{template "content" .}}`. |
| 83 | |
| 84 | Templates are loaded once at startup. Each page template is parsed together with |
| 85 | `layout.html` into its own `*template.Template`. Rendered via `templateSet.render()`. |
| 86 | |
| 87 | ### Template functions (`template.go` FuncMap) |
| 88 | |
| 89 | `shortHash`, `timeAgo`, `formatDate`, `add`, `diffFileName`, `statusLabel`, |
| 90 | `formatSize`, `diffBar`, `langClass`, `parentPath`, `indent`, `autolink`. |
| 91 | |
| 92 | `timeAgo`, `diffBar`, and `autolink` return raw `template.HTML`. |
| 93 | `indent` returns a CSS `padding-left` style string based on tree depth. |
| 94 | `autolink` HTML-escapes text and wraps `http://`/`https://` URLs in `<a>` tags. |
| 95 | |
| 96 | ### Repo home page (`handleSummary` / `handleTree` -> `renderHome`) |
| 97 | |
| 98 | `renderHome(w, repo, ref, blob, activePath)` is the shared renderer for both the |
| 99 | summary page and the tree/file browser. |
| 100 | |
| 101 | - If `ref` is empty, defaults to `getDefaultBranch()` (HEAD's branch name, or first |
| 102 | available branch, or "main") |
| 103 | - Resolves the ref to a commit hash |
| 104 | - Builds the file tree via `buildTreeNodes()` — returns a flat `[]TreeNode` list with |
| 105 | depth markers, expanding only the directories on the active path |
| 106 | - Fetches branches via `getBranches()` for the branch selector dropdown |
| 107 | - Gets README from the active directory (or root) |
| 108 | - Template data struct: `homeData` with fields `DefaultRef`, `Branches`, |
| 109 | `Tree`, `Readme`, `LastCommit`, `ActiveBlob`, `ActivePath`, `IsEmpty` |
| 110 | |
| 111 | The branch selector is a pure-HTML `<details>/<summary>` dropdown (no JS). |
| 112 | |
| 113 | ### Git operations (`git_cli.go`) |
| 114 | |
| 115 | All git operations shell out to the `git` CLI via `exec.Command`. This supports |
| 116 | SHA256 repositories which `go-git` cannot handle. |
| 117 | |
| 118 | Key functions: |
| 119 | - `resolveRef(refStr)` — resolve ref name or HEAD to a commit hash |
| 120 | - `resolveRefAndPath(segments)` — progressively try longer segment prefixes as |
| 121 | ref names (handles refs with slashes like `release/v1.0`) |
| 122 | - `getDefaultBranch()` — HEAD's branch name, fallback to first branch, then "main" |
| 123 | - `getBranches()` / `getTags()` — returns `[]RefInfo` sorted by date descending |
| 124 | - `getTree(hash, path)` — list directory entries, sorted dirs-first then alphabetical |
| 125 | - `buildTreeNodes(hash, activePath)` — recursive flat tree for template rendering |
| 126 | - `getBlob(hash, path)` — file content, up to 1MB, with binary/UTF-8 detection |
| 127 | - `getRawBlob(hash, path)` — raw `io.ReadCloser` for downloads |
| 128 | - `getLog(hash, page, perPage)` — paginated commit list |
| 129 | - `getDiff(hash)` — unified diff with context collapsing (`diffContextLines = 5`) |
| 130 | - `getCommit(hash)` — single commit info |
| 131 | - `getReadme(hash, dir)` — finds readme/README.md/README.txt in a directory |
| 132 | - `isTreePath(hash, path)` — checks if a path is a directory |
| 133 | |
| 134 | Key types (`git.go`): `RepoInfo`, `CommitInfo`, `TreeNode`, `BlobInfo`, `RefInfo`, |
| 135 | `DiffFile`, `DiffHunk`, `DiffLine`, `TreeEntryInfo`, `DiffStats`. |
| 136 | |
| 137 | ### CSS (`static/style.css`) |
| 138 | |
| 139 | CSS custom properties in `:root` for colors and fonts. Two font families: |
| 140 | `RethinkSans` (sans-serif, body text) and `IBM Plex Mono` (monospace, code/hashes). |
| 141 | |
| 142 | Light theme: beige background (`#d3d1d1`), dark text (`#112`), blue links (`#223377`). |
| 143 | Diff colors: green adds (`#c8e6c9`/`#2e7d32`), red deletes (`#ffcdd2`/`#c62828`). |
| 144 | |
| 145 | Font sizes are limited to three values: `0.875rem` (small), `1rem` (base), and |
| 146 | `1.125rem` (large). Do not introduce other font sizes. |
| 147 | |
| 148 | Margins and padding use `0.25rem` increments (e.g. `0.25rem`, `0.5rem`, `0.75rem`, |
| 149 | `1rem`, `1.5rem`, `2rem`). Do not use arbitrary values like `0.3rem` or `0.4rem`. |
| 150 | |
| 151 | There is a single `@media (max-width: 720px)` breakpoint for mobile. To hide an |
| 152 | element on mobile, add the `desktop` class to it in the template HTML. Do not add |
| 153 | per-element CSS rules in the media query for hiding. |
| 154 | |
| 155 | ## Conventions |
| 156 | |
| 157 | - No JavaScript anywhere. All interactivity is pure HTML (links, `<details>`). |
| 158 | The only JS files are syntax highlighting scripts served as static assets. |
| 159 | - All assets are embedded — the binary is fully self-contained. |
| 160 | - Templates use Go's `html/template` with a shared layout pattern. |
| 161 | - Git operations shell out to the `git` CLI (no `go-git` dependency). |
| 162 | - Errors in git operations generally return `nil`/empty rather than propagating to the user |
| 163 | (e.g. `getBranches` errors are silently ignored). |
| 164 | - Handler functions follow the pattern: build page-specific data struct, wrap in `pageData`, |
| 165 | call `s.tmpl.render()`. |
| 166 | - CSS uses a flat structure with class-based selectors, no BEM or similar methodology. |
| 167 | - Repos must have a `public` file in the git directory to be listed. |