Compare commits

..

36 commits

Author SHA1 Message Date
7c96ce304f
fix naming conflict for diff route 2024-01-29 21:30:56 -07:00
5fe7b8e470
add naive global mailmap support 2024-01-29 21:08:08 -07:00
f6c4dfbc41
bump v2.99999-nilix 2023-03-09 12:34:24 -07:00
f139acc54c Merge branch 'zombie_branches' into nilix 2023-03-09 12:33:46 -07:00
02249587f9
don't show orphaned branches in the ref list 2023-03-09 12:33:02 -07:00
3f1059dffb
bump version 0.29999-nilix 2023-02-21 21:34:16 -07:00
adb3eb14bc Merge branch 'raw_link_fix' into nilix 2023-02-21 21:33:27 -07:00
94d07ef8b2
fix raw file link in file view 2023-02-21 21:33:11 -07:00
0aa61d0eb9
bump version to v0.2.999-nilix 2023-02-09 23:31:31 -07:00
5c608d7459 fix title tag and tree .. link 2023-02-09 23:31:09 -07:00
1f7d760e41
fix title tag and tree .. link 2023-02-09 23:30:07 -07:00
49f6bb04d9
remove cdn link from head template 2023-02-07 18:25:49 -07:00
9b97b3b209 Merge branch 'rawfix' into nilix 2023-02-05 12:05:35 -07:00
ad6ece2ad2
use simpler path for raw routes to avoid conflicts with repos with branch called 'raw' 2023-02-05 12:05:15 -07:00
151f060913
nilix config 2023-02-04 10:07:44 -07:00
e6a2ef8560
update readme 2023-02-04 09:54:07 -07:00
30683a7cec
add footer, fix HTML hierarchy 2023-02-04 09:34:21 -07:00
b0c2a8ee38
tree: fit-content for size column 2023-02-04 08:19:23 -07:00
839be3691d
use table for tree view as it is more compatible with eg w3m and netsurf 2023-02-03 17:56:23 -07:00
fd41ddefb0 Merge branch 'linecount_fix' 2023-02-03 17:31:51 -07:00
da18f9eb14
fix file view 2023-02-03 17:30:37 -07:00
3cedf64695
fix buffer overflow in countlines for files greater than 32kb 2023-02-03 17:29:01 -07:00
8eea0d5bbc
fix file view CSS so content fits horizontally and doesn't overscroll 2023-02-02 22:33:15 -07:00
068bda4915
compile templates only once on app start instead of on every page request 2023-02-02 22:05:17 -07:00
907dcc9eeb Merge branch 'refs' 2023-02-02 09:32:50 -07:00
dde087ea2d
use time tag in log 2023-02-02 09:32:31 -07:00
50093251b2
tree: directories first, add size column, fix .. 2023-02-01 23:01:36 -07:00
cd131c0629
update layout for refs page, add pgp signatures to tags and commits
only detailed commits have pgp sigs, not in the log to save bandwidth
2023-02-01 22:43:10 -07:00
584243d358
fix line count for files that don't end in a newline 2023-02-01 22:08:04 -07:00
239d5b2dfa
add raw file routes, link to raw files on file view, add relative link processing to readme code 2023-02-01 22:03:56 -07:00
80ba8a576b Merge branch 'go.mod' 2023-02-01 21:59:39 -07:00
9b99bc7cb8 Merge branch 'taglist' 2023-02-01 21:59:20 -07:00
3fc204c253
sort tags in reverse chronological order and deduplicate 2023-01-31 17:08:03 -07:00
7d3fed52ad
add raw file routes and allow readme to reference relative repo paths 2023-01-31 16:27:22 -07:00
9bb6fb2afc
only add go meta import tag if go.mod exists in repo root 2023-01-31 14:27:48 -07:00
2d263d99a3
add style tweaks: commit hashes, emails, and more cross-browser friendly file view 2023-01-31 14:02:43 -07:00
24 changed files with 553 additions and 233 deletions

View file

@ -1,20 +1,21 @@
repo: repo:
scanPath: /var/www/git scanPath: /home/nilix/src/public/
readme: readme:
- readme - readme
- README - README
- readme.md - readme.md
- README.md - README.md
mainBranch: mainBranch:
- master
- main - main
dirs: dirs:
templates: ./templates templates: ./templates
static: ./static static: ./static
meta: meta:
title: git good title: nilFM hack lab
description: i think it's a skill issue description: are you hacking?
footer: served by legit vVERSION; some of this code might suck — email patches to MAINTAINER
maintainerEmail: nilix@nilfm.cc
server: server:
name: git.icyphox.sh name: hacklab.nilfm.cc
host: 127.0.0.1 host: 0.0.0.0
port: 5555 port: 5555

View file

@ -2,7 +2,9 @@ package config
import ( import (
"fmt" "fmt"
"html/template"
"os" "os"
"strings"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -21,15 +23,35 @@ type Config struct {
Meta struct { Meta struct {
Title string `yaml:"title"` Title string `yaml:"title"`
Description string `yaml:"description"` Description string `yaml:"description"`
Footer string `yaml:"footer,omitempty"`
MaintainerEmail string `yaml:"maintainerEmail,omitempty"`
CompiledFooter template.HTML `yaml:"thisIsNotSupposedToBeHere,omitempty"`
} `yaml:"meta"` } `yaml:"meta"`
Server struct { Server struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty"`
Host string `yaml:"host"` Host string `yaml:"host"`
Port int `yaml:"port"` Port int `yaml:"port"`
} `yaml:"server"` } `yaml:"server"`
Mailmap string `yaml:"mailmap,omitempty"`
} }
func Read(f string) (*Config, error) { func compileFooter(c *Config, version string) {
if c.Meta.Footer != "" {
c.Meta.CompiledFooter = template.HTML(
strings.ReplaceAll(
strings.ReplaceAll(
c.Meta.Footer,
"VERSION",
version),
"MAINTAINER",
fmt.Sprintf(
"<a href='mailto:%s'>%s</a>",
c.Meta.MaintainerEmail,
c.Meta.MaintainerEmail)))
}
}
func Read(f, v string) (*Config, error) {
b, err := os.ReadFile(f) b, err := os.ReadFile(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading config: %w", err) return nil, fmt.Errorf("reading config: %w", err)
@ -40,5 +62,7 @@ func Read(f string) (*Config, error) {
return nil, fmt.Errorf("parsing config: %w", err) return nil, fmt.Errorf("parsing config: %w", err)
} }
compileFooter(&c, v)
return &c, nil return &c, nil
} }

View file

@ -32,6 +32,7 @@ type NiceDiff struct {
Author object.Signature Author object.Signature
This string This string
Parent string Parent string
PGPSignature string
} }
Stat struct { Stat struct {
FilesChanged int FilesChanged int
@ -86,6 +87,9 @@ func (g *GitRepo) Diff() (*NiceDiff, error) {
} }
nd.Commit.Author = c.Author nd.Commit.Author = c.Author
nd.Commit.Message = c.Message nd.Commit.Message = c.Message
if c.PGPSignature != "" {
nd.Commit.PGPSignature = c.PGPSignature
}
for _, d := range diffs { for _, d := range diffs {
ndiff := Diff{} ndiff := Diff{}

View file

@ -2,10 +2,12 @@ package git
import ( import (
"fmt" "fmt"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"os"
"sort"
"strings"
) )
type GitRepo struct { type GitRepo struct {
@ -13,6 +15,21 @@ type GitRepo struct {
h plumbing.Hash h plumbing.Hash
} }
type TagList []*object.Tag
func (self TagList) Len() int {
return len(self)
}
func (self TagList) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
// sorting tags in reverse chronological order
func (self TagList) Less(i, j int) bool {
return self[i].Tagger.When.After(self[j].Tagger.When)
}
func Open(path string, ref string) (*GitRepo, error) { func Open(path string, ref string) (*GitRepo, error) {
var err error var err error
g := GitRepo{} g := GitRepo{}
@ -37,7 +54,8 @@ func Open(path string, ref string) (*GitRepo, error) {
return &g, nil return &g, nil
} }
func (g *GitRepo) Commits() ([]*object.Commit, error) { func (g *GitRepo) Commits(mailmap map[string]string) ([]*object.Commit, error) {
ci, err := g.r.Log(&git.LogOptions{From: g.h}) ci, err := g.r.Log(&git.LogOptions{From: g.h})
if err != nil { if err != nil {
return nil, fmt.Errorf("commits from ref: %w", err) return nil, fmt.Errorf("commits from ref: %w", err)
@ -45,6 +63,18 @@ func (g *GitRepo) Commits() ([]*object.Commit, error) {
commits := []*object.Commit{} commits := []*object.Commit{}
ci.ForEach(func(c *object.Commit) error { ci.ForEach(func(c *object.Commit) error {
if mailmap != nil {
name, ok := mailmap[c.Author.Email]
if ok {
c.Author.Name = name
}
name, ok = mailmap[c.Committer.Email]
if ok {
c.Committer.Name = name
}
}
commits = append(commits, c) commits = append(commits, c)
return nil return nil
}) })
@ -60,7 +90,7 @@ func (g *GitRepo) LastCommit() (*object.Commit, error) {
return c, nil return c, nil
} }
func (g *GitRepo) FileContent(path string) (string, error) { func (g *GitRepo) FileContent(path string, showBinary bool) (string, error) {
c, err := g.r.CommitObject(g.h) c, err := g.r.CommitObject(g.h)
if err != nil { if err != nil {
return "", fmt.Errorf("commit object: %w", err) return "", fmt.Errorf("commit object: %w", err)
@ -78,7 +108,7 @@ func (g *GitRepo) FileContent(path string) (string, error) {
isbin, _ := file.IsBinary() isbin, _ := file.IsBinary()
if !isbin { if showBinary || !isbin {
return file.Contents() return file.Contents()
} else { } else {
return "Not displaying binary file", nil return "Not displaying binary file", nil
@ -94,10 +124,26 @@ func (g *GitRepo) Tags() ([]*object.Tag, error) {
tags := []*object.Tag{} tags := []*object.Tag{}
_ = ti.ForEach(func(t *object.Tag) error { _ = ti.ForEach(func(t *object.Tag) error {
refName := plumbing.NewTagReferenceName(t.Name)
if _, unreachable := g.r.Reference(refName, true); unreachable != nil {
return nil
}
for i, existing := range tags {
if existing.Name == t.Name {
if t.Tagger.When.After(existing.Tagger.When) {
tags[i] = t
}
return nil
}
}
tags = append(tags, t) tags = append(tags, t)
return nil return nil
}) })
var tagList TagList
tagList = tags
sort.Sort(tagList)
return tags, nil return tags, nil
} }
@ -126,3 +172,28 @@ func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
} }
return "", fmt.Errorf("unable to find main branch") return "", fmt.Errorf("unable to find main branch")
} }
func (g *GitRepo) GetMailMap(path string) map[string]string {
f, err := os.ReadFile(path)
if err != nil {
return nil
}
self := make(map[string]string)
fileData := string(f[:])
lines := strings.Split(fileData, "\n")
for _, l := range lines {
parts := strings.Split(l, "<")
if len(parts) != 2 {
continue
}
if parts[1][len(parts[1])-1] != '>' {
continue
}
parts[1] = parts[1][0 : len(parts[1])-1]
self[parts[1]] = strings.Trim(parts[0], " ")
}
return self
}

View file

@ -19,7 +19,7 @@ func (g *GitRepo) FileTree(path string) ([]NiceTree, error) {
} }
if path == "" { if path == "" {
files = makeNiceTree(tree.Entries) files = makeNiceTree(tree)
} else { } else {
o, err := tree.FindEntry(path) o, err := tree.FindEntry(path)
if err != nil { if err != nil {
@ -32,7 +32,7 @@ func (g *GitRepo) FileTree(path string) ([]NiceTree, error) {
return nil, err return nil, err
} }
files = makeNiceTree(subtree.Entries) files = makeNiceTree(subtree)
} }
} }
@ -48,15 +48,17 @@ type NiceTree struct {
IsSubtree bool IsSubtree bool
} }
func makeNiceTree(es []object.TreeEntry) []NiceTree { func makeNiceTree(t *object.Tree) []NiceTree {
nts := []NiceTree{} nts := []NiceTree{}
for _, e := range es { for _, e := range t.Entries {
mode, _ := e.Mode.ToOSFileMode() mode, _ := e.Mode.ToOSFileMode()
sz, _ := t.Size(e.Name)
nts = append(nts, NiceTree{ nts = append(nts, NiceTree{
Name: e.Name, Name: e.Name,
Mode: mode.String(), Mode: mode.String(),
IsFile: e.Mode.IsFile(), IsFile: e.Mode.IsFile(),
Size: sz,
}) })
} }

8
go.mod
View file

@ -8,9 +8,9 @@ require (
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.5.1 github.com/go-git/go-git/v5 v5.5.1
github.com/microcosm-cc/bluemonday v1.0.21 github.com/microcosm-cc/bluemonday v1.0.26
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
golang.org/x/sys v0.3.0 golang.org/x/sys v0.13.0
gopkg.in/yaml.v3 v3.0.0 gopkg.in/yaml.v3 v3.0.0
) )
@ -30,9 +30,9 @@ require (
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect github.com/skeema/knownhosts v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.4.0 // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.7.0 // indirect golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/tools v0.4.0 // indirect golang.org/x/tools v0.4.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )

10
go.sum
View file

@ -59,6 +59,8 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
@ -89,6 +91,8 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -100,6 +104,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@ -120,17 +126,21 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

10
main.go
View file

@ -3,19 +3,22 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"html/template"
"log" "log"
"net/http" "net/http"
"path/filepath"
"git.icyphox.sh/legit/config" "git.icyphox.sh/legit/config"
"git.icyphox.sh/legit/routes" "git.icyphox.sh/legit/routes"
) )
func main() { func main() {
const version string = "0.3.0-nilix"
var cfg string var cfg string
flag.StringVar(&cfg, "config", "./config.yaml", "path to config file") flag.StringVar(&cfg, "config", "./config.yaml", "path to config file")
flag.Parse() flag.Parse()
c, err := config.Read(cfg) c, err := config.Read(cfg, version)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -29,7 +32,10 @@ func main() {
log.Fatalf("unveil: %s", err) log.Fatalf("unveil: %s", err)
} }
mux := routes.Handlers(c) tpath := filepath.Join(c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
mux := routes.Handlers(c, t)
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
log.Println("starting server on", addr) log.Println("starting server on", addr)
log.Fatal(http.ListenAndServe(addr, mux)) log.Fatal(http.ListenAndServe(addr, mux))

11
readme
View file

@ -48,6 +48,8 @@ Example config.yaml:
meta: meta:
title: git good title: git good
description: i think it's a skill issue description: i think it's a skill issue
footer: served with legit vVERSION; email patches to MAINTAINER
maintainerEmail: x@icyphox.sh
server: server:
name: git.icyphox.sh name: git.icyphox.sh
host: 127.0.0.1 host: 127.0.0.1
@ -57,9 +59,14 @@ These options are fairly self-explanatory, but of note are:
• repo.scanPath: where all your git repos live (or die). legit doesn't • repo.scanPath: where all your git repos live (or die). legit doesn't
traverse subdirs yet. traverse subdirs yet.
• repo.readme: readme files to look for. Markdown isn't rendered. • repo.readme: readme files to look for. Markdown is rendered via
BlackFriday and relative links to files in the repo are supported
• repo.mainBranch: main branch names to look for. • repo.mainBranch: main branch names to look for.
• repo.ignore: repos to ignore. • repo.ignore: repos to ignore.
• meta.footer: footer HTML. VERSION is interpolated with the version string
and MAINTAINER is interpolated with a mailto link to meta.maintainerEmail
(can be empty)
• meta.maintainerEmail: probably your own (can be empty)
• server.name: used for go-import meta tags and clone URLs. • server.name: used for go-import meta tags and clone URLs.
@ -67,7 +74,7 @@ NOTES
• Run legit behind a TLS terminating proxy like relayd(8) or nginx. • Run legit behind a TLS terminating proxy like relayd(8) or nginx.
• Cloning only works in bare repos -- this is a limitation inherent to git. You • Cloning only works in bare repos -- this is a limitation inherent to git. You
can still view bare repos just fine in legit. can still view normal repos just fine in legit.
• The default head.html template uses my CDN to fetch fonts -- you may • The default head.html template uses my CDN to fetch fonts -- you may
or may not want this. or may not want this.
• Pushing over https, while supported, is disabled because auth is a • Pushing over https, while supported, is disabled because auth is a

View file

@ -1,6 +1,7 @@
package routes package routes
import ( import (
"html/template"
"net/http" "net/http"
"git.icyphox.sh/legit/config" "git.icyphox.sh/legit/config"
@ -29,9 +30,9 @@ func (d *deps) Multiplex(w http.ResponseWriter, r *http.Request) {
} }
} }
func Handlers(c *config.Config) *flow.Mux { func Handlers(c *config.Config, t *template.Template) *flow.Mux {
mux := flow.New() mux := flow.New()
d := deps{c} d := deps{c, t}
mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
d.Write404(w) d.Write404(w)
@ -41,6 +42,7 @@ func Handlers(c *config.Config) *flow.Mux {
mux.HandleFunc("/static/:file", d.ServeStatic, "GET") mux.HandleFunc("/static/:file", d.ServeStatic, "GET")
mux.HandleFunc("/:name", d.Multiplex, "GET", "POST") mux.HandleFunc("/:name", d.Multiplex, "GET", "POST")
mux.HandleFunc("/:name/tree/:ref/...", d.RepoTree, "GET") mux.HandleFunc("/:name/tree/:ref/...", d.RepoTree, "GET")
mux.HandleFunc("/:name/raw/:ref/...", d.ServeStaticInRepo, "GET")
mux.HandleFunc("/:name/blob/:ref/...", d.FileContent, "GET") mux.HandleFunc("/:name/blob/:ref/...", d.FileContent, "GET")
mux.HandleFunc("/:name/log/:ref", d.Log, "GET") mux.HandleFunc("/:name/log/:ref", d.Log, "GET")
mux.HandleFunc("/:name/commit/:ref", d.Diff, "GET") mux.HandleFunc("/:name/commit/:ref", d.Diff, "GET")

View file

@ -1,6 +1,7 @@
package routes package routes
import ( import (
"bytes"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
@ -20,6 +21,7 @@ import (
type deps struct { type deps struct {
c *config.Config c *config.Config
t *template.Template
} }
func (d *deps) Index(w http.ResponseWriter, r *http.Request) { func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
@ -69,14 +71,11 @@ func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
return infos[j].d.Before(infos[i].d) return infos[j].d.Before(infos[i].d)
}) })
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
data := make(map[string]interface{}) data := make(map[string]interface{})
data["meta"] = d.c.Meta data["meta"] = d.c.Meta
data["info"] = infos data["info"] = infos
if err := t.ExecuteTemplate(w, "index", data); err != nil { if err := d.t.ExecuteTemplate(w, "index", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
@ -97,7 +96,16 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
return return
} }
commits, err := gr.Commits() mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
if err != nil {
d.Write500(w)
log.Println(err)
return
}
mailmap := gr.GetMailMap(d.c.Mailmap)
commits, err := gr.Commits(mailmap)
if err != nil { if err != nil {
d.Write500(w) d.Write500(w)
log.Println(err) log.Println(err)
@ -107,7 +115,7 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
var readmeContent template.HTML var readmeContent template.HTML
for _, readme := range d.c.Repo.Readme { for _, readme := range d.c.Repo.Readme {
ext := filepath.Ext(readme) ext := filepath.Ext(readme)
content, _ := gr.FileContent(readme) content, _ := gr.FileContent(readme, false)
if len(content) > 0 { if len(content) > 0 {
switch ext { switch ext {
case ".md", ".mkd", ".markdown": case ".md", ".mkd", ".markdown":
@ -116,7 +124,7 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
blackfriday.WithExtensions(blackfriday.CommonExtensions), blackfriday.WithExtensions(blackfriday.CommonExtensions),
) )
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
readmeContent = template.HTML(html) readmeContent = template.HTML(transformRelativeURLs(string(html), name, mainBranch))
default: default:
readmeContent = template.HTML( readmeContent = template.HTML(
fmt.Sprintf(`<pre>%s</pre>`, content), fmt.Sprintf(`<pre>%s</pre>`, content),
@ -130,16 +138,6 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
log.Printf("no readme found for %s", name) log.Printf("no readme found for %s", name)
} }
mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
if err != nil {
d.Write500(w)
log.Println(err)
return
}
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
if len(commits) >= 3 { if len(commits) >= 3 {
commits = commits[:3] commits = commits[:3]
} }
@ -151,8 +149,10 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
data["commits"] = commits data["commits"] = commits
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
data["servername"] = d.c.Server.Name data["servername"] = d.c.Server.Name
data["gomod"] = isGoModule(gr)
data["meta"] = d.c.Meta
if err := t.ExecuteTemplate(w, "repo", data); err != nil { if err := d.t.ExecuteTemplate(w, "repo", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
@ -189,6 +189,7 @@ func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
data["ref"] = ref data["ref"] = ref
data["parent"] = treePath data["parent"] = treePath
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
data["dotdot"] = filepath.Dir(treePath)
d.listFiles(files, data, w) d.listFiles(files, data, w)
return return
@ -211,12 +212,13 @@ func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
return return
} }
contents, err := gr.FileContent(treePath) contents, err := gr.FileContent(treePath, false)
data := make(map[string]any) data := make(map[string]any)
data["name"] = name data["name"] = name
data["ref"] = ref data["ref"] = ref
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
data["path"] = treePath data["path"] = treePath
data["raw"] = fmt.Sprintf("/%s/raw/%s/%s", name, ref, treePath)
d.showFile(contents, data, w) d.showFile(contents, data, w)
return return
@ -237,24 +239,24 @@ func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
return return
} }
commits, err := gr.Commits() mailmap := gr.GetMailMap(d.c.Mailmap)
commits, err := gr.Commits(mailmap)
if err != nil { if err != nil {
d.Write500(w) d.Write500(w)
log.Println(err) log.Println(err)
return return
} }
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
data := make(map[string]interface{}) data := make(map[string]interface{})
data["commits"] = commits data["commits"] = commits
data["meta"] = d.c.Meta data["meta"] = d.c.Meta
data["name"] = name data["name"] = name
data["ref"] = ref data["ref"] = ref
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
data["log"] = true
if err := t.ExecuteTemplate(w, "log", data); err != nil { if err := d.t.ExecuteTemplate(w, "log", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
@ -275,15 +277,20 @@ func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
return return
} }
mailmap := gr.GetMailMap(d.c.Mailmap)
diff, err := gr.Diff() diff, err := gr.Diff()
if err != nil { if err != nil {
d.Write500(w) d.Write500(w)
log.Println(err) log.Println(err)
return return
} }
tpath := filepath.Join(d.c.Dirs.Templates, "*") authorname, ok := mailmap[diff.Commit.Author.Email]
t := template.Must(template.ParseGlob(tpath)) if ok {
diff.Commit.Author.Name = authorname
}
data := make(map[string]interface{}) data := make(map[string]interface{})
@ -295,7 +302,7 @@ func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
data["ref"] = ref data["ref"] = ref
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
if err := t.ExecuteTemplate(w, "commit", data); err != nil { if err := d.t.ExecuteTemplate(w, "commit", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
@ -328,9 +335,6 @@ func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
return return
} }
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
data := make(map[string]interface{}) data := make(map[string]interface{})
data["meta"] = d.c.Meta data["meta"] = d.c.Meta
@ -339,12 +343,32 @@ func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
data["tags"] = tags data["tags"] = tags
data["desc"] = getDescription(path) data["desc"] = getDescription(path)
if err := t.ExecuteTemplate(w, "refs", data); err != nil { if err := d.t.ExecuteTemplate(w, "refs", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
} }
func (d *deps) ServeStaticInRepo(w http.ResponseWriter, r *http.Request) {
f := flow.Param(r.Context(), "...")
p := flow.Param(r.Context(), "name")
ref := flow.Param(r.Context(), "ref")
repoPath := filepath.Clean(filepath.Join(d.c.Repo.ScanPath, p))
gr, err := git.Open(repoPath, ref)
if err != nil {
d.Write500(w)
return
}
contents, err := gr.FileContent(f, true)
if err != nil {
d.Write500(w)
return
}
http.ServeContent(w, r, filepath.Base(f), time.Unix(0, 0), bytes.NewReader([]byte(contents)))
}
func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) { func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
f := flow.Param(r.Context(), "file") f := flow.Param(r.Context(), "file")
f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f)) f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))

View file

@ -2,42 +2,33 @@ package routes
import ( import (
"bytes" "bytes"
"html/template"
"io" "io"
"log" "log"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
"git.icyphox.sh/legit/git" "git.icyphox.sh/legit/git"
) )
func (d *deps) Write404(w http.ResponseWriter) { func (d *deps) Write404(w http.ResponseWriter) {
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
w.WriteHeader(404) w.WriteHeader(404)
if err := t.ExecuteTemplate(w, "404", nil); err != nil { if err := d.t.ExecuteTemplate(w, "404", nil); err != nil {
log.Printf("404 template: %s", err) log.Printf("404 template: %s", err)
} }
} }
func (d *deps) Write500(w http.ResponseWriter) { func (d *deps) Write500(w http.ResponseWriter) {
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
w.WriteHeader(500) w.WriteHeader(500)
if err := t.ExecuteTemplate(w, "500", nil); err != nil { if err := d.t.ExecuteTemplate(w, "500", nil); err != nil {
log.Printf("500 template: %s", err) log.Printf("500 template: %s", err)
} }
} }
func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) { func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
data["files"] = files data["files"] = files
data["meta"] = d.c.Meta data["meta"] = d.c.Meta
if err := t.ExecuteTemplate(w, "tree", data); err != nil { if err := d.t.ExecuteTemplate(w, "tree", data); err != nil {
log.Println(err) log.Println(err)
return return
} }
@ -45,15 +36,23 @@ func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.Respo
func countLines(r io.Reader) (int, error) { func countLines(r io.Reader) (int, error) {
buf := make([]byte, 32*1024) buf := make([]byte, 32*1024)
bufLen := 0
count := 0 count := 0
nl := []byte{'\n'} nl := []byte{'\n'}
for { for {
c, err := r.Read(buf) c, err := r.Read(buf)
if c > 0 {
bufLen += c
}
count += bytes.Count(buf[:c], nl) count += bytes.Count(buf[:c], nl)
switch { switch {
case err == io.EOF: case err == io.EOF:
/* handle last line not having a newline at the end */
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
count++
}
return count, nil return count, nil
case err != nil: case err != nil:
return 0, err return 0, err
@ -62,9 +61,6 @@ func countLines(r io.Reader) (int, error) {
} }
func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) { func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) {
tpath := filepath.Join(d.c.Dirs.Templates, "*")
t := template.Must(template.ParseGlob(tpath))
lc, err := countLines(strings.NewReader(content)) lc, err := countLines(strings.NewReader(content))
if err != nil { if err != nil {
// Non-fatal, we'll just skip showing line numbers in the template. // Non-fatal, we'll just skip showing line numbers in the template.
@ -82,7 +78,7 @@ func (d *deps) showFile(content string, data map[string]any, w http.ResponseWrit
data["content"] = content data["content"] = content
data["meta"] = d.c.Meta data["meta"] = d.c.Meta
if err := t.ExecuteTemplate(w, "file", data); err != nil { if err := d.t.ExecuteTemplate(w, "file", data); err != nil {
log.Println(err) log.Println(err)
return return
} }

View file

@ -1,10 +1,19 @@
package routes package routes
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"git.icyphox.sh/legit/git"
) )
func isGoModule(gr *git.GitRepo) bool {
_, err := gr.FileContent("go.mod", false)
return err == nil
}
func getDescription(path string) (desc string) { func getDescription(path string) (desc string) {
db, err := os.ReadFile(filepath.Join(path, "description")) db, err := os.ReadFile(filepath.Join(path, "description"))
if err == nil { if err == nil {
@ -15,6 +24,13 @@ func getDescription(path string) (desc string) {
return return
} }
func transformRelativeURLs(html, repoName, mainBranch string) string {
return strings.ReplaceAll(
html,
"=\"./",
fmt.Sprintf("=\"/%s/raw/%s/", repoName, mainBranch))
}
func (d *deps) isIgnored(name string) bool { func (d *deps) isIgnored(name string) bool {
for _, i := range d.c.Repo.Ignore { for _, i := range d.c.Repo.Ignore {
if name == i { if name == i {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,29 +1,18 @@
:root {
--light: #f4f4f4;
--cyan: #509c93;
--light-gray: #eee;
--medium-gray: #ddd;
--gray: #6a6a6a;
--dark: #444;
--darker: #222;
--sans-font: "InterVar", -apple-system, BlinkMacSystemFont, "Roboto", "Segoe UI", sans-serif;
--display-font: "InterDisplay", -apple-system, BlinkMacSystemFont, "Roboto", "Segoe UI", sans-serif;
--mono-font: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', 'Roboto Mono', Menlo, Consolas, monospace;
}
html { html {
background: var(--light); background: #000;
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
font-family: var(--sans-font); font-family: serif;
color: #c9c9c9;
font-size: 14px;
} }
pre { pre {
font-family: var(--mono-font); font-family: monospace;
} }
::selection { ::selection {
background: var(--medium-gray); background: #1f9b92;
color: #000;
opacity: 0.3; opacity: 0.3;
} }
@ -34,20 +23,26 @@ pre {
} }
body { body {
max-width: 750px;
padding: 0 13px; padding: 0 13px;
margin: 40px auto; margin: 40px auto;
} }
main, footer { main {
font-size: 1rem; font-size: 1rem;
padding: 0; padding: 0;
line-height: 160%; line-height: 160%;
overflow-x: auto;
}
footer {
font-size: 85%;
padding: 1em;
text-align: center;
} }
main h1, h2, h3, strong { main h1, h2, h3, strong {
font-family: var(--display-font);
font-weight: 500; font-weight: 500;
width:fit-content;
} }
strong { strong {
@ -67,6 +62,14 @@ main h2, h3 {
padding: 20px 0 15px 0; padding: 20px 0 15px 0;
} }
main h3 {
width: 80vw;
max-width: 500px;
margin-left: auto;
margin-right: auto;
text-transform: capitalize;
}
nav { nav {
padding: 0.4rem 0 1.5rem 0; padding: 0.4rem 0 1.5rem 0;
} }
@ -92,19 +95,22 @@ a {
} }
a { a {
color: var(--darker); color: #1f9b92;
border-bottom: 1.5px solid var(--medium-gray); text-decoration: none;
font-weight:bold;
} }
a:hover { a:hover {
border-bottom: 1.5px solid var(--gray); color: #fff;
} }
.index { .index {
margin: 0px auto;
width: 80vw;
padding-top: 2em; padding-top: 2em;
display: grid; display: grid;
grid-template-columns: 6em 1fr minmax(0, 7em); grid-template-columns: auto 1fr auto;
grid-row-gap: 0.5em; grid-gap: 0.5em;
min-width: 0; min-width: 0;
} }
@ -113,54 +119,83 @@ a:hover {
} }
.clone-url pre { .clone-url pre {
color: var(--dark); color: #1f9b92;
white-space: pre-wrap; white-space: pre-wrap;
} }
.desc { .desc {
font-weight: normal;
color: var(--gray);
font-style: italic;
} }
.tree { .tree {
display: grid; margin: 0px 10vw;
grid-template-columns: 8em minmax(0, 1fr); max-width: 80vw;
grid-row-gap: 0.5em; }
grid-column-gap: 1em;
min-width: 0; .tree td {
padding: 0.5em 0.5ch;
font-size: 14px;
box-sizing: content-box;
}
.mode, .size {
font-family: monospace;
width: 10ch;
white-space:nowrap;
}
.size {
text-align: right;
width: fit-content;
} }
.log { .log {
width:fit-content;
max-width: 80vw;
margin: 0 auto;
display: grid; display: grid;
grid-template-columns: 20rem minmax(0, 1fr); grid-template-columns: 1fr auto;
grid-row-gap: 0.8em; grid-row-gap: 0.8em;
grid-column-gap: 8rem; grid-column-gap: 8rem;
margin-bottom: 2em; margin-bottom: 2em;
padding-bottom: 1em; padding-left: 1ch;
border-bottom: 1.5px solid var(--medium-gray); border-left: solid 2px #797979;
} }
.log pre { .commit-hash, .commit-email {
font-family: monospace;
}
.log pre, .commit pre {
white-space: pre-wrap; white-space: pre-wrap;
font-family: serif;
} }
.mode { .mode {
font-family: var(--mono-font); font-family: monospace;
} }
.readme pre { .readme pre {
white-space: pre-wrap; white-space: pre-wrap;
} }
.readme { .readme * {
background: var(--light-gray); margin: 0 auto;
padding: 0.5rem; width: 80vw;
max-width: 500px;
}
.readme h1, .readme h2, .readme h3, .readme h4, .readme h5, .readme h6 {
text-transform: capitalize;
} }
.readme ul { .readme ul {
padding: revert; padding: revert;
} }
.readme table, .readme pre { width:fit-content;max-width:100vw;margin-left:max(10vw, max(0px, calc(50vw - 250px)));transform:translateX(max(min(0px, calc(min(40vw, 250px) - 50%)), min(-10vw, calc(250px - 50%)))); }
.readme p, .readme ul, .readme ol { line-height:150%;margin-top:1em;margin-bottom:1em; }
.readme li { margin-top:0.5em;margin-bottom:0.5em;width:100%; }
.readme code, .readme pre { background:#00263b;color:#93a1a1;padding:0.25em; }
.readme pre { white-space:pre;overflow-x:auto; }
.readme blockquote {padding-left:1ch;line-height:150%;border-left:solid 2px #797979; }
.readme img { .readme img {
max-width: 100%; max-width: 100%;
@ -169,10 +204,11 @@ a:hover {
.diff { .diff {
margin: 1rem 0 1rem 0; margin: 1rem 0 1rem 0;
padding: 1rem 0 1rem 0; padding: 1rem 0 1rem 0;
border-bottom: 1.5px solid var(--medium-gray);
} }
.diff pre { .diff pre {
background: #002b36;
color: #93a1a1;
overflow: scroll; overflow: scroll;
} }
@ -182,10 +218,14 @@ a:hover {
.commit-email:before { .commit-email:before {
content: '<'; content: '<';
color: #c9c9c9 !important;
font-weight: normal;
} }
.commit-email:after { .commit-email:after {
content: '>'; content: '>';
color: #c9c9c9 !important;
font-weight: normal;
} }
.commit { .commit {
@ -203,30 +243,77 @@ a:hover {
} }
.diff-add { .diff-add {
color: green; color: #1f9b92;
} }
.diff-del { .diff-del {
color: red; color: #dc322f;
} }
.diff-noop { .diff-noop {
color: var(--gray);
} }
.ref { .refs {
font-family: var(--display-font); width: 80vw;
font-size: 14px; max-width: 500px;
color: var(--gray); margin: 0 auto;
display: inline-block; }
padding-top: 0.7em;
.refs ul {
list-style: none;
display: inline;
}
.refs ul li {
display: inline;
margin: 0;
margin-left: 1ch;
}
.branches {
padding-left: 2ch;
border-left: 2px solid #797979;
}
.refs h4 {
display: inline;
}
.tags time {
float: right;
font-size: 85%;
}
.commit-info time {
display: block;
}
time {
font-style: italic;
}
.tag-entry {
padding-left: 2ch;
border-left: 2px solid #797979;
margin-bottom: 2em;
} }
.refs pre { .refs pre {
font-family: serif;
white-space: pre-wrap; white-space: pre-wrap;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.tag-entry details pre, .commit-info details pre {
font-family: monospace;
font-size: 1.0rem;
}
.ref {
font-family: monospace;
font-size: 75%;
}
.refs strong { .refs strong {
padding-right: 1em; padding-right: 1em;
} }
@ -238,29 +325,53 @@ a:hover {
-webkit-user-select: none; -webkit-user-select: none;
-o-user-select: none; -o-user-select: none;
user-select: none; user-select: none;
display:inline-block;
flex-direction:column;
margin-right: 1ch;
font-size: 14px;
font-family:monospace;
text-align:right;
} }
.file-wrapper { .file-wrapper {
display: flex; display: table;
flex-direction: row; margin-top: 0.5em;
grid-template-columns: 1rem minmax(0, 1fr);
gap: 1rem;
padding: 0.5rem;
background: var(--light-gray);
} }
.file-content { .file-content {
background: var(--light-gray); color: #93a1a1;
background: #002b36;
overflow-y: hidden; overflow-y: hidden;
overflow-x: auto; overflow-x: auto;
font-size: 14px;
}
@supports (display: flex) {
.line-numbers {
display: flex;
}
.file-content {
display: inline-block;
width: 100%;
}
.file-wrapper {
width: 100%;
}
.file-wrapper tr {
display: flex;
flex-direction: row;
width: 100%;
}
} }
.diff-type { .diff-type {
color: var(--gray); color: #000;
background: #c9c9c9;
padding: 2px 4px;
} }
.commit-info { .commit-info {
color: var(--gray);
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
font-size: 0.85rem; font-size: 0.85rem;
} }
@ -288,7 +399,13 @@ a:hover {
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
} }
pre { }
font-size: 0.8rem;
@media (max-width: 420px) {
.tree {
grid-template-columns: 8em 1fr;
}
.tree .size {
display: none;
} }
} }

View file

@ -1,9 +1,8 @@
{{ define "commit" }} {{ define "commit" }}
<html> <html>
{{ template "head" . }} {{ template "head" . }}
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
<section class="commit"> <section class="commit">
@ -11,13 +10,18 @@
{{- .commit.Message -}} {{- .commit.Message -}}
</pre> </pre>
<div class="commit-info"> <div class="commit-info">
{{ .commit.Author.Name }} <span class="commit-email">{{ .commit.Author.Email}}</span> {{ .commit.Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .commit.Author.Email}}</a>
<div>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> <time>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
{{ if .commit.PGPSignature }}
<details><summary>PGP Signature</summary>
<pre>{{ .commit.PGPSignature }}</pre>
</details>
{{ end }}
</div> </div>
<div> <div>
<strong>commit</strong> <strong>commit</strong>
<p><a href="/{{ .name }}/commit/{{ .commit.This }}"> <p><a href="/{{ .name }}/commit/{{ .commit.This }}" class="commit-hash">
{{ .commit.This }} {{ .commit.This }}
</a> </a>
</p> </p>
@ -26,7 +30,7 @@
{{ if .commit.Parent }} {{ if .commit.Parent }}
<div> <div>
<strong>parent</strong> <strong>parent</strong>
<p><a href="/{{ .name }}/commit/{{ .commit.Parent }}"> <p><a href="/{{ .name }}/commit/{{ .commit.Parent }}" class="commit-hash">
{{ .commit.Parent }} {{ .commit.Parent }}
</a></p> </a></p>
</div> </div>
@ -99,6 +103,7 @@
{{ end }} {{ end }}
</section> </section>
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

View file

@ -1,26 +1,29 @@
{{ define "file" }} {{ define "file" }}
<html> <html>
{{ template "head" . }} {{ template "head" . }}
<title>{{.name }} &mdash; {{ .path }}</title>
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
<p>{{ .path }}</p> <p>{{ .path }} (<a href="{{ .raw }}">raw</a>)</p>
<div class="file-wrapper"> <table class="file-wrapper">
<div class="line-numbers"> <tbody><tr>
<td class="line-numbers">
<pre>
{{- range .linecount }} {{- range .linecount }}
<a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a> <a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a>
{{- end -}} {{- end -}}
</div> </pre>
<div class="file-content"> </td>
<span></span> <td class="file-content">
<pre> <pre>
{{- .content -}} {{- .content -}}
</pre> </pre>
</div> </td>
</tbody></tr>
</table>
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

7
templates/footer.html Normal file
View file

@ -0,0 +1,7 @@
{{ define "footer" }}
{{ if .CompiledFooter }}
<footer>
<span>{{ .CompiledFooter }}</span>
</footer>
{{ end }}
{{ end }}

View file

@ -3,9 +3,28 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
<link rel="stylesheet" href="https://cdn.icyphox.sh/fonts/inter.css" type="text/css">
<link rel="icon" type="image/png" size="32x32" href="/static/legit.png"> <link rel="icon" type="image/png" size="32x32" href="/static/legit.png">
{{ if .servername }} {{ if .parent }}
<title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .parent }}/</title>
{{ else if .path }}
<title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .path }}</title>
{{ else if .files }}
<title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }})</title>
{{ else if .commit }}
<title>{{ .meta.Title }} &mdash; {{ .name }}: {{ .commit.This }}</title>
{{ else if .branches }}
<title>{{ .meta.Title }} &mdash; {{ .name }}: refs</title>
{{ else if .commits }}
{{ if .log }}
<title>{{ .meta.Title }} &mdash; {{ .name }}: log</title>
{{ else }}
<title>{{ .meta.Title }} &mdash; {{ .name }}</title>
{{ end }}
{{ else }}
<title>{{ .meta.Title }}</title>
{{ end }}
{{ if and .servername .gomod }}
<meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> <meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}">
{{ end }} {{ end }}
<!-- other meta tags here --> <!-- other meta tags here -->

View file

@ -1,16 +1,11 @@
{{ define "index" }} {{ define "index" }}
<html> <html>
{{ template "head" . }} {{ template "head" . }}
<body>
<title>
{{ .meta.Title }}
</title>
<header> <header>
<h1>{{ .meta.Title }}</h1> <h1>{{ .meta.Title }}</h1>
<h2>{{ .meta.Description }}</h2> <h2>{{ .meta.Description }}</h2>
</header> </header>
<body>
<main> <main>
<div class="index"> <div class="index">
{{ range .info }} {{ range .info }}
@ -20,6 +15,8 @@
{{ end }} {{ end }}
</div> </div>
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

View file

@ -1,29 +1,25 @@
{{ define "log" }} {{ define "log" }}
<html> <html>
{{ template "head" . }} {{ template "head" . }}
<title>
{{ .name }} &mdash; log
</title>
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
{{ $repo := .name }} {{ $repo := .name }}
<div class="log"> <div class="log">
{{ range .commits }} {{ range .commits }}
<div> <div>
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}">{{ slice .Hash.String 0 8 }}</a></div> <div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
<pre>{{ .Message }}</pre> <pre>{{ .Message }}</pre>
</div> </div>
<div class="commit-info"> <div class="commit-info">
{{ .Author.Name }} <span class="commit-email">{{ .Author.Email }}</span> {{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> <time>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
</div> </div>
{{ end }} {{ end }}
</div> </div>
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

View file

@ -2,41 +2,49 @@
<html> <html>
{{ template "head" . }} {{ template "head" . }}
<title>
{{ .name }} &mdash; refs
</title>
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
{{ $name := .name }} {{ $name := .name }}
<h3>branches</h3> <h3>branches</h3>
<div class="refs"> <div class="refs branches">
{{ range .branches }} {{ range .branches }}
<div> <div class="branch-entry">
<strong>{{ .Name.Short }}</strong> <h4>{{ .Name.Short }}</h4>
<a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a> <ul>
<a href="/{{ $name }}/log/{{ .Name.Short }}">log</a> <li><a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a></li>
<li><a href="/{{ $name }}/log/{{ .Name.Short }}">log</a></li>
</ul>
</div> </div>
{{ end }} {{ end }}
</div> </div>
{{ if .tags }} {{ if .tags }}
<h3>tags</h3> <h3>tags</h3>
<div class="refs"> <div class="refs tags">
{{ range .tags }} {{ range .tags }}
<div> <div class="tag-entry">
<strong>{{ .Name }}</strong> <h4>{{ .Name }}</h4>
<a href="/{{ $name }}/tree/{{ .Name }}/">browse</a> <time>{{ .Tagger.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
<a href="/{{ $name }}/log/{{ .Name }}">log</a> <ul>
<li><a href="/{{ $name }}/tree/{{ .Name }}/">browse</a></li>
<li><a href="/{{ $name }}/log/{{ .Name }}">log</a></li>
</ul>
{{ if .Message }} {{ if .Message }}
<pre>{{ .Message }}</pre> <pre>{{ .Message }}</pre>
</div>
{{ end }} {{ end }}
{{ if .PGPSignature }}
<details><summary>PGP Signature</summary>
<pre>{{ .PGPSignature }}</pre>
</details>
{{ end }}
</div>
{{ end }} {{ end }}
</div> </div>
{{ end }} {{ end }}
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

View file

@ -1,27 +1,20 @@
{{ define "repo" }} {{ define "repo" }}
<html> <html>
<title>{{ .name }}
{{ if .parent }}
&mdash; {{ .parent }}
{{ end }}
</title>
{{ template "head" . }} {{ template "head" . }}
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
{{ $repo := .name }} {{ $repo := .name }}
<div class="log"> <div class="log">
{{ range .commits }} {{ range .commits }}
<div> <div>
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}">{{ slice .Hash.String 0 8 }}</a></div> <div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
<pre>{{ .Message }}</pre> <pre>{{ .Message }}</pre>
</div> </div>
<div class="commit-info"> <div class="commit-info">
{{ .Author.Name }} <span class="commit-email">{{ .Author.Email }}</span> {{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> <time>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
</div> </div>
{{ end }} {{ end }}
</div> </div>
@ -38,6 +31,8 @@ git clone https://{{ .servername }}/{{ .name }}
</pre> </pre>
</div> </div>
</main> </main>
{{ template "footer" .meta }}
</body> </body>
</html> </html>
{{ end }} {{ end }}

View file

@ -1,44 +1,53 @@
{{ define "tree" }} {{ define "tree" }}
<html> <html>
<title>{{ .name }}
{{ if .parent }}
&mdash; {{ .parent }}
{{ end }}
</title>
{{ template "head" . }} {{ template "head" . }}
{{ template "repoheader" . }}
<body> <body>
{{ template "repoheader" . }}
{{ template "nav" . }} {{ template "nav" . }}
<main> <main>
{{ $repo := .name }} {{ $repo := .name }}
{{ $ref := .ref }} {{ $ref := .ref }}
{{ $parent := .parent }} {{ $parent := .parent }}
<div class="tree"> <table class="tree">
{{ if $parent }} {{ if $parent }}
<div></div> <tr>
<div><a href="../">..</a></div> <td></td>
<td></td>
<td><a href="/{{ $repo }}/tree/{{ $ref }}/{{ .dotdot }}">..</a></td>
</tr>
{{ end }} {{ end }}
{{ range .files }} {{ range .files }}
<div class="mode">{{ .Mode }}</div> {{ if not .IsFile }}
<div> <tr>
{{ if .IsFile }} <td class="mode">{{ .Mode }}</td>
{{ if $parent }} <td class="size">{{ .Size }}</td>
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> <td>
{{ else }}
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a>
{{ end }}
{{ else }}
{{ if $parent }} {{ if $parent }}
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> <a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>
{{ else }} {{ else }}
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a> <a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a>
{{ end }} {{ end }}
</td>
</tr>
{{ end }} {{ end }}
</div>
{{ end }} {{ end }}
</div> {{ range .files }}
{{ if .IsFile }}
<tr>
<td class="mode">{{ .Mode }}</td>
<td class="size">{{ .Size }}</td>
<td>
{{ if $parent }}
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>
{{ else }}
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a>
{{ end }}
</td>
</tr>
{{ end }}
{{ end }}
</table>
<article> <article>
<pre> <pre>
{{- if .readme }}{{ .readme }}{{- end -}} {{- if .readme }}{{ .readme }}{{- end -}}
@ -46,5 +55,6 @@
</article> </article>
</main> </main>
</body> </body>
{{ template "footer" .meta }}
</html> </html>
{{ end }} {{ end }}