package main
import (
"bytes"
"fmt"
"net"
"net/http"
"os"
"path"
"syscall"
"unsafe"
)
var (
repoRoot = "/home/git"
gitBin = "/usr/bin/git"
addr = "127.0.0.1:3000"
host = "git.smesh.lol"
gitBackend = "/usr/lib/git-core/git-http-backend"
)
// ── exec ────────────────────────────────────────────────────────
// run forks, execs bin with args, captures stdout via pipe.
// Uses raw clone syscall to bypass moxie's missing runtime fork hooks.
func run(bin string, args ...string) ([]byte, error) {
var p [2]int
if err := syscall.Pipe(p[:]); err != nil {
return nil, err
}
argv := []string{:0:len(args)+1}
argv = append(argv, bin)
argv = append(argv, args...)
binp, _ := syscall.BytePtrFromString(bin)
argvp, _ := syscall.SlicePtrFromStrings(argv)
envp, _ := syscall.SlicePtrFromStrings([]string{"PATH=/usr/bin:/bin"})
r1, _, e := syscall.RawSyscall(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD), 0, 0)
if e != 0 {
syscall.Close(p[0])
syscall.Close(p[1])
return nil, e
}
if r1 == 0 {
// child
syscall.Dup2(p[1], 1)
syscall.Close(p[0])
syscall.Close(p[1])
devnull, _ := syscall.Open("/dev/null", syscall.O_RDWR, 0)
syscall.Dup2(devnull, 0)
syscall.Dup2(devnull, 2)
syscall.Close(devnull)
syscall.RawSyscall(syscall.SYS_EXECVE,
uintptr(unsafe.Pointer(binp)),
uintptr(unsafe.Pointer(&argvp[0])),
uintptr(unsafe.Pointer(&envp[0])))
syscall.Exit(127)
}
pid := int(r1)
syscall.Close(p[1])
var buf bytes.Buffer
tmp := []byte{:8192}
for {
n, _ := syscall.Read(p[0], tmp)
if n <= 0 {
break
}
buf.Write(tmp[:n])
}
syscall.Close(p[0])
var ws syscall.WaitStatus
syscall.Wait4(pid, &ws, 0, nil)
if ws.Exited() && ws.ExitStatus() != 0 {
return buf.Bytes(), fmt.Errorf("exit %d", ws.ExitStatus())
}
return buf.Bytes(), nil
}
func gitCmd(repoDir string, args ...string) ([]byte, error) {
full := []string{:0:len(args)+2}
full = append(full, "--git-dir="+repoDir)
full = append(full, args...)
return run(gitBin, full...)
}
// runIO forks, execs bin with args using given env, optionally piping stdin.
func runIO(env []string, stdin []byte, bin string, args ...string) ([]byte, error) {
var pout [2]int
if err := syscall.Pipe(pout[:]); err != nil {
return nil, err
}
hasIn := stdin != nil
var pin [2]int
if hasIn {
if err := syscall.Pipe(pin[:]); err != nil {
syscall.Close(pout[0])
syscall.Close(pout[1])
return nil, err
}
}
argv := []string{:0:len(args)+1}
argv = append(argv, bin)
argv = append(argv, args...)
binp, _ := syscall.BytePtrFromString(bin)
argvp, _ := syscall.SlicePtrFromStrings(argv)
envp, _ := syscall.SlicePtrFromStrings(env)
r1, _, e := syscall.RawSyscall(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD), 0, 0)
if e != 0 {
syscall.Close(pout[0])
syscall.Close(pout[1])
if hasIn {
syscall.Close(pin[0])
syscall.Close(pin[1])
}
return nil, e
}
if r1 == 0 {
syscall.Dup2(pout[1], 1)
syscall.Close(pout[0])
syscall.Close(pout[1])
if hasIn {
syscall.Dup2(pin[0], 0)
syscall.Close(pin[0])
syscall.Close(pin[1])
}
devnull, _ := syscall.Open("/dev/null", syscall.O_RDWR, 0)
if !hasIn {
syscall.Dup2(devnull, 0)
}
syscall.Dup2(devnull, 2)
syscall.Close(devnull)
syscall.RawSyscall(syscall.SYS_EXECVE,
uintptr(unsafe.Pointer(binp)),
uintptr(unsafe.Pointer(&argvp[0])),
uintptr(unsafe.Pointer(&envp[0])))
syscall.Exit(127)
}
pid := int(r1)
syscall.Close(pout[1])
if hasIn {
syscall.Close(pin[0])
for off := 0; off < len(stdin); {
n, err := syscall.Write(pin[1], stdin[off:])
if n <= 0 || err != nil {
break
}
off += n
}
syscall.Close(pin[1])
}
var buf bytes.Buffer
tmp := []byte{:32768}
for {
n, _ := syscall.Read(pout[0], tmp)
if n <= 0 {
break
}
buf.Write(tmp[:n])
}
syscall.Close(pout[0])
var ws syscall.WaitStatus
syscall.Wait4(pid, &ws, 0, nil)
if ws.Exited() && ws.ExitStatus() != 0 {
return buf.Bytes(), fmt.Errorf("exit %d", ws.ExitStatus())
}
return buf.Bytes(), nil
}
// ── html helpers ────────────────────────────────────────────────
func esc(s string) string {
s = bytes.ReplaceAll(s, "&", "&")
s = bytes.ReplaceAll(s, "<", "<")
s = bytes.ReplaceAll(s, ">", ">")
s = bytes.ReplaceAll(s, "\"", """)
return s
}
// ── markdown ────────────────────────────────────────────────────
// renderMD converts markdown source to HTML.
func renderMD(src, linkName string) string {
var b bytes.Buffer
lines := bytes.Split(src, "\n")
n := len(lines)
i := 0
var closeTags []string // stack of tags to close
flush := func() {
for j := len(closeTags) - 1; j >= 0; j-- {
b.WriteString(closeTags[j])
}
closeTags = nil
}
inState := func(tag string) bool {
for _, t := range closeTags {
if t == tag {
return true
}
}
return false
}
lastWasHeading := false
for i < n {
line := lines[i]
// fenced code block — count opening backticks for GFM nesting
if bytes.HasPrefix(line, "```") {
flush()
lastWasHeading = false
fenceLen := 0
for fenceLen < len(line) && line[fenceLen] == '`' {
fenceLen++
}
lang := bytes.TrimSpace(line[fenceLen:])
b.WriteString(`
`)
if lang != "" {
_ = lang // no syntax highlighting, just consume
}
i++
for i < n {
cl := bytes.TrimSpace(lines[i])
// closing fence: >= same backtick count, nothing else
ticks := 0
for ticks < len(cl) && cl[ticks] == '`' {
ticks++
}
if ticks >= fenceLen && allChar(cl, '`') {
break
}
b.WriteString(esc(lines[i]))
b.WriteByte('\n')
i++
}
b.WriteString(`
`)
i++ // skip closing ```
continue
}
// blank line — close open blocks
if bytes.TrimSpace(line) == "" {
flush()
i++
continue
}
// heading
if line[0] == '#' {
flush()
lvl := 0
for lvl < len(line) && line[lvl] == '#' {
lvl++
}
if lvl <= 6 && lvl < len(line) && line[lvl] == ' ' {
text := bytes.TrimSpace(line[lvl:])
text = bytes.TrimRight(text, " #")
b.WriteString(fmt.Sprintf("%s\n", lvl, mdInline(text, linkName), lvl))
lastWasHeading = true
i++
continue
}
}
// horizontal rule — skip if immediately after a heading (already has border-bottom)
trimmed := bytes.TrimSpace(line)
if len(trimmed) >= 3 && (allChar(trimmed, '-') || allChar(trimmed, '*') || allChar(trimmed, '_')) {
flush()
if !lastWasHeading {
b.WriteString("
\n")
}
lastWasHeading = false
i++
continue
}
// table (line contains unescaped | and next line is separator)
if hasUnescapedPipe(line) && i+1 < n && isTableSep(lines[i+1]) {
flush()
b.WriteString("\n")
for _, cell := range splitTableRow(line) {
b.WriteString("| " + mdInline(bytes.TrimSpace(cell), linkName) + " | ")
}
b.WriteString("
\n\n")
i += 2 // skip header + separator
for i < n && hasUnescapedPipe(lines[i]) && bytes.TrimSpace(lines[i]) != "" {
b.WriteString("")
for _, cell := range splitTableRow(lines[i]) {
b.WriteString("| " + mdInline(bytes.TrimSpace(cell), linkName) + " | ")
}
b.WriteString("
\n")
i++
}
b.WriteString("
\n")
continue
}
// blockquote
if bytes.HasPrefix(line, "> ") || line == ">" {
if !inState("\n") {
flush()
b.WriteString("")
closeTags = append(closeTags, "
\n")
}
text := ""
if len(line) > 2 {
text = line[2:]
}
b.WriteString(mdInline(text, linkName) + "\n")
i++
continue
}
// unordered list
if (bytes.HasPrefix(line, "- ") || bytes.HasPrefix(line, "* ") || bytes.HasPrefix(line, "+ ")) && len(line) > 2 {
if !inState("\n") {
flush()
b.WriteString("\n")
closeTags = append(closeTags, "
\n")
}
b.WriteString("" + mdInline(line[2:], linkName) + "\n")
i++
continue
}
// ordered list
if isOL(line) {
if !inState("\n") {
flush()
b.WriteString("\n")
closeTags = append(closeTags, "
\n")
}
dot := bytes.IndexByte(line, '.')
b.WriteString("" + mdInline(bytes.TrimSpace(line[dot+1:]), linkName) + "\n")
i++
continue
}
// paragraph
if !inState("\n") {
flush()
b.WriteString("")
closeTags = append(closeTags, "
\n")
} else {
b.WriteByte('\n')
}
b.WriteString(mdInline(line, linkName))
i++
}
flush()
return b.String()
}
// mdInline processes inline markdown: code, bold, italic, links, images.
func mdInline(s, linkName string) string {
s = esc(s) // HTML-escape first; markdown punctuation (*,[,],`,!) is not affected
var b bytes.Buffer
i := 0
for i < len(s) {
// backtick code span
if s[i] == '`' {
end := bytes.IndexByte(s[i+1:], '`')
if end >= 0 {
b.WriteString("" + s[i+1:i+1+end] + "")
i += end + 2
continue
}
}
// linked image [](link-url) — render as text link to link-url
if s[i] == '[' && i+1 < len(s) && s[i+1] == '!' && i+2 < len(s) && s[i+2] == '[' {
if innerText, _, innerAdv := parseLink(s[i+2:]); innerAdv > 0 {
pos := i + 2 + innerAdv
if pos+1 < len(s) && s[pos] == ']' && s[pos+1] == '(' {
end := bytes.IndexByte(s[pos+2:], ')')
if end >= 0 {
url := s[pos+2 : pos+2+end]
b.WriteString(`` + innerText + ``)
i = pos + 2 + end + 1
continue
}
}
}
}
// image  — inline if relative, link if external
if s[i] == '!' && i+1 < len(s) && s[i+1] == '[' {
if text, url, advance := parseLink(s[i+1:]); advance > 0 {
if bytes.HasPrefix(url, "http://") || bytes.HasPrefix(url, "https://") || bytes.HasPrefix(url, "//") {
b.WriteString(`` + text + ``)
} else {
raw := "/" + linkName + "/raw/" + bytes.TrimPrefix(url, "./")
b.WriteString(`
`)
}
i += 1 + advance
continue
}
}
// link [text](url) — rewrite relative paths to gitweb blob URLs
if s[i] == '[' {
if text, url, advance := parseLink(s[i:]); advance > 0 {
href := url
if linkName != "" && !bytes.HasPrefix(url, "http://") && !bytes.HasPrefix(url, "https://") && !bytes.HasPrefix(url, "//") && !bytes.HasPrefix(url, "#") && !bytes.HasPrefix(url, "/") {
href = "/" + linkName + "/blob/" + bytes.TrimPrefix(url, "./")
}
b.WriteString(`` + text + ``)
i += advance
continue
}
}
// bold **text**
if i+1 < len(s) && s[i] == '*' && s[i+1] == '*' {
end := bytes.Index(s[i+2:], "**")
if end >= 0 {
b.WriteString("" + s[i+2:i+2+end] + "")
i += end + 4
continue
}
}
// bold __text__
if i+1 < len(s) && s[i] == '_' && s[i+1] == '_' {
end := bytes.Index(s[i+2:], "__")
if end >= 0 {
b.WriteString("" + s[i+2:i+2+end] + "")
i += end + 4
continue
}
}
// italic *text*
if s[i] == '*' && (i+1 < len(s) && s[i+1] != '*') {
end := bytes.IndexByte(s[i+1:], '*')
if end > 0 {
b.WriteString("" + s[i+1:i+1+end] + "")
i += end + 2
continue
}
}
// italic _text_
if s[i] == '_' && (i+1 < len(s) && s[i+1] != '_') {
end := bytes.IndexByte(s[i+1:], '_')
if end > 0 {
b.WriteString("" + s[i+1:i+1+end] + "")
i += end + 2
continue
}
}
// backslash escape — \| \* \_ \` \[ \] \\ render as literal
if s[i] == '\\' && i+1 < len(s) {
next := s[i+1]
if next == '|' || next == '*' || next == '_' || next == '`' || next == '[' || next == ']' || next == '\\' {
b.WriteByte(next)
i += 2
continue
}
}
b.WriteByte(s[i])
i++
}
return b.String()
}
// parseLink parses [text](url) starting at s[0]=='['.
// Returns text, url, and total bytes consumed. Returns 0 advance on failure.
func parseLink(s string) (string, string, int) {
if len(s) < 4 || s[0] != '[' {
return "", "", 0
}
closeBracket := bytes.IndexByte(s[1:], ']')
if closeBracket < 0 {
return "", "", 0
}
closeBracket++ // adjust for offset
if closeBracket+1 >= len(s) || s[closeBracket+1] != '(' {
return "", "", 0
}
closeParen := bytes.IndexByte(s[closeBracket+2:], ')')
if closeParen < 0 {
return "", "", 0
}
text := s[1:closeBracket]
url := s[closeBracket+2 : closeBracket+2+closeParen]
return text, url, closeBracket + 2 + closeParen + 1
}
func allChar(s string, c byte) bool {
for i := 0; i < len(s); i++ {
if s[i] != c && s[i] != ' ' {
return false
}
}
return true
}
func isOL(line string) bool {
i := 0
for i < len(line) && line[i] >= '0' && line[i] <= '9' {
i++
}
return i > 0 && i < len(line)-1 && line[i] == '.' && line[i+1] == ' '
}
// hasUnescapedPipe returns true if the line contains | that isn't
// preceded by \ and isn't inside a backtick code span.
func hasUnescapedPipe(line string) bool {
inCode := false
for i := 0; i < len(line); i++ {
if line[i] == '`' {
inCode = !inCode
} else if line[i] == '\\' && i+1 < len(line) && line[i+1] == '|' {
i++ // skip escaped pipe
} else if line[i] == '|' && !inCode {
return true
}
}
return false
}
func isTableSep(line string) bool {
t := bytes.TrimSpace(line)
if !bytes.Contains(t, "|") {
return false
}
for _, c := range t {
if c != '|' && c != '-' && c != ':' && c != ' ' {
return false
}
}
return true
}
func splitTableRow(line string) []string {
line = bytes.TrimSpace(line)
line = bytes.Trim(line, "|")
// Split on | but not \| (escaped pipe) or | inside backtick spans.
// Uses index slicing into the original line — avoids bytes.Buffer
// aliasing where string=[]byte causes Reset() to overwrite prior cells.
var cells []string
start := 0
inCode := false
for i := 0; i < len(line); i++ {
if line[i] == '`' {
inCode = !inCode
} else if line[i] == '\\' && i+1 < len(line) && line[i+1] == '|' {
i++ // skip escaped pipe — mdInline handles \| → |
} else if line[i] == '|' && !inCode {
cells = append(cells, line[start:i])
start = i + 1
}
}
cells = append(cells, line[start:])
return cells
}
const css = `
:root{--bg:#fff;--bg2:#eaeaea;--fg:#111;--accent:#7b3fe4;--muted:#666;--border:#bbb}
.dark{--bg:#000;--bg2:#111;--fg:#e0e0e0;--accent:#f59e0b;--muted:#666;--border:#333}
*{box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font:14px/1.6 'Fira Code',monospace;margin:0;padding:20px 40px;max-width:960px}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
h1{color:var(--fg);font-size:20px;border-bottom:1px solid var(--border);padding-bottom:6px}
h2{color:var(--fg);font-size:16px;margin-top:24px}
pre{background:var(--bg2);color:var(--fg);padding:14px;border-radius:3px;overflow-x:auto;border:1px solid var(--border)}
.ls{list-style:none;padding:0;margin:0}
.ls li{padding:3px 0;border-bottom:1px solid var(--border)}
.ls .d a{color:var(--accent)}
.ls .d a::before{content:"d ";color:var(--muted)}
.ls .f a::before{content:" ";color:var(--muted)}
nav{margin-bottom:16px;color:var(--muted)}
nav a{margin-right:4px}
.desc{color:var(--muted);margin-left:12px;font-size:12px}
.info{color:var(--muted);font-size:12px;margin-top:4px}
.md{line-height:1.7}
.md h1{font-size:22px;margin-top:28px}
.md h2{font-size:18px;margin-top:24px}
.md h3{font-size:15px;margin-top:20px}
.md h4,.md h5,.md h6{font-size:14px;color:var(--muted);margin-top:16px}
.md p{margin:10px 0}
.md code{background:var(--bg2);padding:2px 5px;border-radius:3px;font-size:13px}
.md pre{margin:12px 0}
.md pre code{background:none;padding:0}
.md blockquote{border-left:3px solid var(--border);margin:10px 0;padding:4px 16px;color:var(--muted)}
.md ul,.md ol{padding-left:24px;margin:8px 0}
.md li{margin:3px 0}
.md hr{border:none;border-top:1px solid var(--border);margin:20px 0}
.md img{max-width:100%}
.md table{border-collapse:collapse;margin:12px 0}
.md th,.md td{border:1px solid var(--border);padding:6px 12px}
.md th{background:var(--bg2)}
.readme-box{position:relative}
.readme-box input{display:none}
.readme-box .readme-body{max-height:50vh;overflow:hidden}
.readme-box input:checked+.readme-body{max-height:none}
.readme-box .readme-body::after{content:"";position:absolute;bottom:28px;left:0;right:0;height:80px;background:linear-gradient(transparent,var(--bg));pointer-events:none}
.readme-box input:checked+.readme-body::after{display:none}
.readme-box label{display:block;text-align:center;padding:6px;color:var(--accent);cursor:pointer;border-top:1px solid var(--border);margin-top:4px;font-size:13px}
.readme-box label:hover{opacity:0.8}
.readme-box input:checked~label .show{display:none}
.readme-box input:not(:checked)~label .hide{display:none}
.hdr{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.hdr svg{width:56px;height:56px;flex-shrink:0}
.hdr h1{border:none;padding:0;margin:0}
.theme-btn{position:fixed;top:12px;right:12px;background:none;border:none;color:var(--fg);cursor:pointer;padding:8px;opacity:0.6}
.theme-btn:hover{opacity:1}
.theme-btn svg{width:22px;height:22px}
.when-dark{display:none}
.dark .when-light{display:none}
.dark .when-dark{display:inline}
.ln{color:var(--muted)}
::-webkit-scrollbar{width:14px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:7px;border:3px solid var(--bg)}
::-webkit-scrollbar-thumb:hover{background:var(--muted)}
*{scrollbar-width:auto;scrollbar-color:var(--border) var(--bg)}
`
const logo = ``
const themeJS = ``
const sunIcon = ``
const moonIcon = ``
const toggleJS = ``
func page(title, body string) []byte {
var b bytes.Buffer
b.WriteString(``)
b.WriteString(``)
b.WriteString(`` + esc(title) + ``)
b.WriteString(``)
b.WriteString(themeJS)
b.WriteString(``)
b.WriteString(``)
b.WriteString(body)
b.WriteString(toggleJS)
b.WriteString(``)
return b.Bytes()
}
// ── repo discovery ──────────────────────────────────────────────
func findRepos() []string {
entries, err := os.ReadDir(repoRoot)
if err != nil {
return nil
}
var repos []string
for _, e := range entries {
if !e.IsDir() {
continue
}
if _, err := os.Stat(path.Join(repoRoot, e.Name(), "HEAD")); err == nil {
repos = append(repos, e.Name())
}
}
return repos
}
func cleanName(s string) string {
return bytes.TrimSuffix(s, ".git")
}
// resolveRepo maps a URL name to the actual repo directory name.
// Accepts both "smesh" and "smesh.git".
func resolveRepo(name string) (string, string) {
// try exact match first
rp := path.Join(repoRoot, name)
if _, err := os.Stat(path.Join(rp, "HEAD")); err == nil {
return name, rp
}
// try with .git suffix
gitName := name + ".git"
rp = path.Join(repoRoot, gitName)
if _, err := os.Stat(path.Join(rp, "HEAD")); err == nil {
return gitName, rp
}
return "", ""
}
func repoDesc(repoDir string) string {
data, err := os.ReadFile(path.Join(repoDir, "description"))
if err != nil {
return ""
}
s := bytes.TrimSpace(string(data))
if bytes.HasPrefix(s, "Unnamed repository") {
return ""
}
return s
}
// ── ref resolution ──────────────────────────────────────────────
// defaultRef finds the actual default branch for a bare repo.
// HEAD may point to a ref that doesn't exist (e.g. refs/heads/master
// when the only branch is main).
func defaultRef(repoDir string) string {
// read HEAD to get the symbolic ref
data, err := os.ReadFile(path.Join(repoDir, "HEAD"))
if err != nil {
return "HEAD"
}
s := bytes.TrimSpace(string(data))
if bytes.HasPrefix(s, "ref: ") {
ref := s[5:]
// check if this ref exists as a loose ref file
if _, err := os.Stat(path.Join(repoDir, ref)); err == nil {
return ref
}
// check packed-refs
packed, err := os.ReadFile(path.Join(repoDir, "packed-refs"))
if err == nil && bytes.Contains(string(packed), ref) {
return ref
}
}
// HEAD ref is broken — find first available branch
refsDir := path.Join(repoDir, "refs", "heads")
entries, err := os.ReadDir(refsDir)
if err == nil {
for _, e := range entries {
if !e.IsDir() {
return "refs/heads/" + e.Name()
}
}
}
// try packed-refs as last resort
packed, err := os.ReadFile(path.Join(repoDir, "packed-refs"))
if err == nil {
for _, line := range bytes.Split(string(packed), "\n") {
line = bytes.TrimSpace(line)
if line == "" || line[0] == '#' || line[0] == '^' {
continue
}
fields := bytes.Fields(line)
if len(fields) >= 2 && bytes.HasPrefix(fields[1], "refs/heads/") {
return fields[1]
}
}
}
return "HEAD"
}
// ── tree parsing ────────────────────────────────────────────────
type entry struct {
name string
typ string // "tree" or "blob"
}
func lsTree(repoDir, ref, treePath string) ([]entry, error) {
args := []string{"ls-tree", ref}
if treePath != "" {
args = append(args, treePath+"/")
}
out, err := gitCmd(repoDir, args...)
if err != nil {
return nil, err
}
prefix := ""
if treePath != "" {
prefix = treePath + "/"
}
var entries []entry
for _, line := range bytes.Split(string(out), "\n") {
line = bytes.TrimSpace(line)
if line == "" {
continue
}
// format: \t
tab := bytes.IndexByte(line, '\t')
if tab < 0 {
continue
}
name := line[tab+1:]
fields := bytes.Fields(line[:tab])
if len(fields) < 2 {
continue
}
// strip directory prefix
if prefix != "" && bytes.HasPrefix(name, prefix) {
name = name[len(prefix):]
}
entries = append(entries, entry{name: name, typ: fields[1]})
}
return entries, nil
}
func mimeType(name string) string {
ext := name
if i := bytes.LastIndexByte(name, '.'); i >= 0 {
ext = name[i:]
}
switch bytes.ToLower(ext) {
case ".html", ".htm":
return "text/html; charset=utf-8"
case ".css":
return "text/css; charset=utf-8"
case ".js":
return "text/javascript; charset=utf-8"
case ".json":
return "application/json"
case ".png":
return "image/png"
case ".jpg", ".jpeg":
return "image/jpeg"
case ".gif":
return "image/gif"
case ".svg":
return "image/svg+xml"
case ".ico":
return "image/x-icon"
case ".woff2":
return "font/woff2"
case ".wasm":
return "application/wasm"
case ".pdf":
return "application/pdf"
case ".xml":
return "application/xml"
case ".txt", ".md", ".go", ".rs", ".py", ".sh", ".yml", ".yaml", ".toml",
".c", ".h", ".cpp", ".java", ".rb", ".pl", ".lua", ".sql", ".diff",
".patch", ".conf", ".cfg", ".ini", ".log", ".csv", ".tsx", ".ts",
".jsx", ".vue", ".svelte", ".mx":
return "text/plain; charset=utf-8"
}
return "application/octet-stream"
}
func isGitProto(sub string) bool {
return sub == "info" || sub == "git-upload-pack" || sub == "git-receive-pack" ||
sub == "HEAD" || sub == "objects"
}
// ── handlers ────────────────────────────────────────────────────
func handler(w http.ResponseWriter, r *http.Request) {
p := bytes.Trim(r.URL.Path, "/")
if p == "" {
serveIndex(w)
return
}
parts := bytes.SplitN(p, "/", 3)
urlName := parts[0]
// git smart HTTP protocol
if len(parts) > 1 && isGitProto(parts[1]) {
serveGit(w, r)
return
}
repo, rp := resolveRepo(urlName)
if repo == "" {
http.NotFound(w, r)
return
}
// go-get meta tag support
if r.URL.Query().Get("go-get") == "1" {
serveGoGet(w, urlName, repo)
return
}
// use the clean URL name for links
linkName := cleanName(repo)
if len(parts) == 1 {
serveRepo(w, linkName, repo, rp)
return
}
sub := ""
if len(parts) > 2 {
sub = parts[2]
}
switch parts[1] {
case "tree":
serveTree(w, linkName, repo, rp, sub)
case "blob":
serveBlob(w, linkName, repo, rp, sub)
case "raw":
serveRaw(w, linkName, rp, sub)
default:
http.NotFound(w, r)
}
}
func serveGoGet(w http.ResponseWriter, urlName, repo string) {
mod := cleanName(urlName)
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `go get`,
esc(host), esc(mod), esc(host), esc(repo))
}
func serveIndex(w http.ResponseWriter) {
repos := findRepos()
var b bytes.Buffer
b.WriteString(``)
for _, r := range repos {
name := cleanName(r)
desc := repoDesc(path.Join(repoRoot, r))
b.WriteString(`- ` + esc(name) + ``)
if desc != "" {
b.WriteString(`` + esc(desc) + ``)
}
b.WriteString(`
`)
}
b.WriteString(`
`)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(page("repos", b.String()))
}
func serveRepo(w http.ResponseWriter, linkName, repo, repoDir string) {
ref := defaultRef(repoDir)
var b bytes.Buffer
b.WriteString(``)
b.WriteString(`` + esc(linkName) + `
`)
// description
if desc := repoDesc(repoDir); desc != "" {
b.WriteString(`` + esc(desc) + `
`)
}
// clone urls
b.WriteString(`git clone https://` + esc(host) + `/` + esc(repo) + `
`)
b.WriteString(`git clone ssh://git@` + esc(host) + `:2222/~/` + esc(repo) + `
`)
// readme
for _, readme := range []string{"README.md", "README", "README.txt", "readme.md"} {
data, err := gitCmd(repoDir, "show", ref+":"+readme)
if err == nil && len(data) > 0 {
b.WriteString(``)
b.WriteString(`
`)
b.WriteString(`
`)
if bytes.HasSuffix(readme, ".md") {
b.WriteString(`
` + renderMD(string(data), linkName) + `
`)
} else {
b.WriteString(`
` + esc(readme) + `
`)
b.WriteString(`
` + esc(string(data)) + `
`)
}
b.WriteString(`
`)
b.WriteString(`
`)
b.WriteString(`
`)
break
}
}
// file listing
entries, err := lsTree(repoDir, ref, "")
if err != nil {
b.WriteString(`empty repository — push to get started
`)
} else if len(entries) > 0 {
b.WriteString(`files
`)
writeEntriesList(&b, linkName, entries, "")
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(page(linkName, b.String()))
}
func writeEntries(b *bytes.Buffer, linkName, repoDir, ref, treePath string) {
entries, err := lsTree(repoDir, ref, treePath)
if err != nil {
b.WriteString(`` + esc(err.Error()) + `
`)
return
}
writeEntriesList(b, linkName, entries, treePath)
}
func writeEntriesList(b *bytes.Buffer, linkName string, entries []entry, treePath string) {
b.WriteString(``)
for _, e := range entries {
if e.typ != "tree" {
continue
}
fp := e.name
if treePath != "" {
fp = treePath + "/" + e.name
}
b.WriteString(`- ` + esc(e.name) + `/
`)
}
for _, e := range entries {
if e.typ == "tree" {
continue
}
fp := e.name
if treePath != "" {
fp = treePath + "/" + e.name
}
b.WriteString(`- ` + esc(e.name) + `
`)
}
b.WriteString(`
`)
}
func breadcrumb(linkName, treePath string) string {
var b bytes.Buffer
b.WriteString(``)
return b.String()
}
func serveTree(w http.ResponseWriter, linkName, repo, repoDir, treePath string) {
ref := defaultRef(repoDir)
var b bytes.Buffer
b.WriteString(breadcrumb(linkName, treePath))
b.WriteString(`` + esc(treePath) + `
`)
writeEntries(&b, linkName, repoDir, ref, treePath)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(page(linkName+" / "+treePath, b.String()))
}
func serveBlob(w http.ResponseWriter, linkName, repo, repoDir, blobPath string) {
ref := defaultRef(repoDir)
data, err := gitCmd(repoDir, "show", ref+":"+blobPath)
if err != nil {
http.NotFound(w, nil)
return
}
var b bytes.Buffer
b.WriteString(breadcrumb(linkName, blobPath))
fname := blobPath
if idx := bytes.LastIndexByte(blobPath, '/'); idx >= 0 {
fname = blobPath[idx+1:]
}
b.WriteString(`` + esc(fname) + ` raw
`)
if bytes.HasSuffix(blobPath, ".md") {
b.WriteString(`` + renderMD(string(data), linkName) + `
`)
} else {
lines := bytes.Split(string(data), "\n")
b.WriteString(``)
for i, line := range lines {
ln := fmt.Sprintf("%4d ", i+1)
b.WriteString(`` + ln + `` + esc(line) + "\n")
}
b.WriteString(``)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(page(linkName+" / "+blobPath, b.String()))
}
func serveRaw(w http.ResponseWriter, linkName, repoDir, filePath string) {
if filePath == "" {
http.NotFound(w, nil)
return
}
ref := defaultRef(repoDir)
data, err := gitCmd(repoDir, "show", ref+":"+filePath)
if err != nil {
http.NotFound(w, nil)
return
}
w.Header().Set("Content-Type", mimeType(filePath))
w.Header().Set("Cache-Control", "max-age=300")
w.Write(data)
}
func serveGit(w http.ResponseWriter, r *http.Request) {
env := []string{
"PATH=/usr/bin:/bin",
"GIT_PROJECT_ROOT=" + repoRoot,
"GIT_HTTP_EXPORT_ALL=1",
"REQUEST_METHOD=" + r.Method,
"QUERY_STRING=" + r.URL.RawQuery,
"PATH_INFO=" + r.URL.Path,
"SERVER_PROTOCOL=HTTP/1.1",
}
if ct := r.Header.Get("Content-Type"); ct != "" {
env = append(env, "CONTENT_TYPE="+ct)
}
if cl := r.Header.Get("Content-Length"); cl != "" {
env = append(env, "CONTENT_LENGTH="+cl)
}
if proto := r.Header.Get("Git-Protocol"); proto != "" {
env = append(env, "GIT_PROTOCOL="+proto)
}
ra := r.RemoteAddr
if i := bytes.LastIndexByte(ra, ':'); i >= 0 {
ra = ra[:i]
}
env = append(env, "REMOTE_ADDR="+ra)
var stdin []byte
if r.Method == "POST" && r.Body != nil {
var body bytes.Buffer
tmp := []byte{:8192}
for {
n, err := r.Body.Read(tmp)
if n > 0 {
body.Write(tmp[:n])
}
if err != nil {
break
}
}
if body.Len() > 0 {
stdin = body.Bytes()
}
}
out, err := runIO(env, stdin, gitBackend)
if err != nil && len(out) == 0 {
http.Error(w, "git backend error", 500)
return
}
// parse CGI response: headers \n\n body
sep := bytes.Index(out, []byte("\r\n\r\n"))
skip := 4
if sep < 0 {
sep = bytes.Index(out, []byte("\n\n"))
skip = 2
}
if sep < 0 {
http.Error(w, "bad cgi response", 500)
return
}
code := 200
for _, line := range bytes.Split(string(out[:sep]), "\n") {
line = bytes.TrimRight(line, "\r")
idx := bytes.IndexByte(line, ':')
if idx < 0 {
continue
}
key := bytes.TrimSpace(line[:idx])
val := bytes.TrimSpace(line[idx+1:])
if bytes.EqualFold(key, "Status") {
if len(val) >= 3 {
code = 0
for j := 0; j < 3; j++ {
if val[j] >= '0' && val[j] <= '9' {
code = code*10 + int(val[j]-'0')
}
}
}
} else {
w.Header().Set(key, val)
}
}
w.WriteHeader(code)
w.Write(out[sep+skip:])
}
// ── main ────────────────────────────────────────────────────────
func main() {
for i := 1; i < len(os.Args); i++ {
switch os.Args[i] {
case "-repos":
i++
if i < len(os.Args) {
repoRoot = os.Args[i]
}
case "-listen":
i++
if i < len(os.Args) {
addr = os.Args[i]
}
case "-git":
i++
if i < len(os.Args) {
gitBin = os.Args[i]
}
case "-host":
i++
if i < len(os.Args) {
host = os.Args[i]
}
case "-git-backend":
i++
if i < len(os.Args) {
gitBackend = os.Args[i]
}
}
}
// auto-detect git-http-backend path
if _, err := os.Stat(gitBackend); err != nil {
for _, p := range []string{"/usr/lib/git-core/git-http-backend", "/usr/libexec/git-core/git-http-backend"} {
if _, err := os.Stat(p); err == nil {
gitBackend = p
break
}
}
}
fmt.Printf("gitweb %s repos=%s\n", addr, repoRoot)
// Parse addr ourselves to bypass moxie SplitHostPort codegen bug.
tcpAddr := &net.TCPAddr{Port: 3000}
if i := bytes.LastIndexByte(addr, ':'); i >= 0 {
h := addr[:i]
p := addr[i+1:]
if h != "" {
tcpAddr.IP = net.ParseIP(h)
}
port := 0
for _, c := range p {
port = port*10 + int(c-'0')
}
tcpAddr.Port = port
}
ln, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
http.HandleFunc("/", handler)
if err := http.Serve(ln, nil); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}