Compare commits
No commits in common. "nilix" and "master" have entirely different histories.
24 changed files with 236 additions and 556 deletions
13
config.yaml
13
config.yaml
|
@ -1,21 +1,20 @@
|
||||||
repo:
|
repo:
|
||||||
scanPath: /home/nilix/src/public/
|
scanPath: /var/www/git
|
||||||
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: nilFM hack lab
|
title: git good
|
||||||
description: are you hacking?
|
description: i think it's a skill issue
|
||||||
footer: served by legit vVERSION; some of this code might suck — email patches to MAINTAINER
|
|
||||||
maintainerEmail: nilix@nilfm.cc
|
|
||||||
server:
|
server:
|
||||||
name: hacklab.nilfm.cc
|
name: git.icyphox.sh
|
||||||
host: 0.0.0.0
|
host: 127.0.0.1
|
||||||
port: 5555
|
port: 5555
|
||||||
|
|
|
@ -2,9 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
@ -23,35 +21,15 @@ 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 compileFooter(c *Config, version string) {
|
func Read(f string) (*Config, error) {
|
||||||
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)
|
||||||
|
@ -62,7 +40,5 @@ func Read(f, v 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ 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
|
||||||
|
@ -87,9 +86,6 @@ 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{}
|
||||||
|
|
79
git/git.go
79
git/git.go
|
@ -2,12 +2,10 @@ 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 {
|
||||||
|
@ -15,21 +13,6 @@ 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{}
|
||||||
|
@ -54,8 +37,7 @@ func Open(path string, ref string) (*GitRepo, error) {
|
||||||
return &g, nil
|
return &g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitRepo) Commits(mailmap map[string]string) ([]*object.Commit, error) {
|
func (g *GitRepo) Commits() ([]*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)
|
||||||
|
@ -63,18 +45,6 @@ func (g *GitRepo) Commits(mailmap map[string]string) ([]*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
|
||||||
})
|
})
|
||||||
|
@ -90,7 +60,7 @@ func (g *GitRepo) LastCommit() (*object.Commit, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitRepo) FileContent(path string, showBinary bool) (string, error) {
|
func (g *GitRepo) FileContent(path string) (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)
|
||||||
|
@ -108,7 +78,7 @@ func (g *GitRepo) FileContent(path string, showBinary bool) (string, error) {
|
||||||
|
|
||||||
isbin, _ := file.IsBinary()
|
isbin, _ := file.IsBinary()
|
||||||
|
|
||||||
if showBinary || !isbin {
|
if !isbin {
|
||||||
return file.Contents()
|
return file.Contents()
|
||||||
} else {
|
} else {
|
||||||
return "Not displaying binary file", nil
|
return "Not displaying binary file", nil
|
||||||
|
@ -124,26 +94,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,28 +126,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
10
git/tree.go
10
git/tree.go
|
@ -19,7 +19,7 @@ func (g *GitRepo) FileTree(path string) ([]NiceTree, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
files = makeNiceTree(tree)
|
files = makeNiceTree(tree.Entries)
|
||||||
} 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)
|
files = makeNiceTree(subtree.Entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +48,15 @@ type NiceTree struct {
|
||||||
IsSubtree bool
|
IsSubtree bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeNiceTree(t *object.Tree) []NiceTree {
|
func makeNiceTree(es []object.TreeEntry) []NiceTree {
|
||||||
nts := []NiceTree{}
|
nts := []NiceTree{}
|
||||||
|
|
||||||
for _, e := range t.Entries {
|
for _, e := range es {
|
||||||
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
8
go.mod
|
@ -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.26
|
github.com/microcosm-cc/bluemonday v1.0.21
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
golang.org/x/sys v0.13.0
|
golang.org/x/sys v0.3.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.14.0 // indirect
|
golang.org/x/crypto v0.4.0 // indirect
|
||||||
golang.org/x/mod v0.7.0 // indirect
|
golang.org/x/mod v0.7.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.4.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
10
go.sum
|
@ -59,8 +59,6 @@ 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=
|
||||||
|
@ -91,8 +89,6 @@ 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=
|
||||||
|
@ -104,8 +100,6 @@ 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=
|
||||||
|
@ -126,21 +120,17 @@ 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
10
main.go
|
@ -3,22 +3,19 @@ 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, version)
|
c, err := config.Read(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -32,10 +29,7 @@ func main() {
|
||||||
log.Fatalf("unveil: %s", err)
|
log.Fatalf("unveil: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tpath := filepath.Join(c.Dirs.Templates, "*")
|
mux := routes.Handlers(c)
|
||||||
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
11
readme
|
@ -48,8 +48,6 @@ 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
|
||||||
|
@ -59,14 +57,9 @@ 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 is rendered via
|
• repo.readme: readme files to look for. Markdown isn't rendered.
|
||||||
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.
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +67,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 normal repos just fine in legit.
|
can still view bare 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
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.icyphox.sh/legit/config"
|
"git.icyphox.sh/legit/config"
|
||||||
|
@ -30,9 +29,9 @@ func (d *deps) Multiplex(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handlers(c *config.Config, t *template.Template) *flow.Mux {
|
func Handlers(c *config.Config) *flow.Mux {
|
||||||
mux := flow.New()
|
mux := flow.New()
|
||||||
d := deps{c, t}
|
d := deps{c}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -42,7 +41,6 @@ func Handlers(c *config.Config, t *template.Template) *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")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
|
@ -21,7 +20,6 @@ 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) {
|
||||||
|
@ -71,11 +69,14 @@ 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 := d.t.ExecuteTemplate(w, "index", data); err != nil {
|
if err := t.ExecuteTemplate(w, "index", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -96,16 +97,7 @@ func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
|
commits, err := gr.Commits()
|
||||||
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)
|
||||||
|
@ -115,7 +107,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, false)
|
content, _ := gr.FileContent(readme)
|
||||||
if len(content) > 0 {
|
if len(content) > 0 {
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".md", ".mkd", ".markdown":
|
case ".md", ".mkd", ".markdown":
|
||||||
|
@ -124,7 +116,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(transformRelativeURLs(string(html), name, mainBranch))
|
readmeContent = template.HTML(html)
|
||||||
default:
|
default:
|
||||||
readmeContent = template.HTML(
|
readmeContent = template.HTML(
|
||||||
fmt.Sprintf(`<pre>%s</pre>`, content),
|
fmt.Sprintf(`<pre>%s</pre>`, content),
|
||||||
|
@ -138,6 +130,16 @@ 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]
|
||||||
}
|
}
|
||||||
|
@ -149,10 +151,8 @@ 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 := d.t.ExecuteTemplate(w, "repo", data); err != nil {
|
if err := t.ExecuteTemplate(w, "repo", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,6 @@ 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
|
||||||
|
@ -212,13 +211,12 @@ func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := gr.FileContent(treePath, false)
|
contents, err := gr.FileContent(treePath)
|
||||||
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
|
||||||
|
@ -239,24 +237,24 @@ func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mailmap := gr.GetMailMap(d.c.Mailmap)
|
commits, err := gr.Commits()
|
||||||
|
|
||||||
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 := d.t.ExecuteTemplate(w, "log", data); err != nil {
|
if err := t.ExecuteTemplate(w, "log", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -277,20 +275,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
authorname, ok := mailmap[diff.Commit.Author.Email]
|
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||||
if ok {
|
t := template.Must(template.ParseGlob(tpath))
|
||||||
diff.Commit.Author.Name = authorname
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
@ -302,7 +295,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 := d.t.ExecuteTemplate(w, "commit", data); err != nil {
|
if err := t.ExecuteTemplate(w, "commit", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -335,6 +328,9 @@ 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
|
||||||
|
@ -343,32 +339,12 @@ 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 := d.t.ExecuteTemplate(w, "refs", data); err != nil {
|
if err := 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))
|
||||||
|
|
|
@ -2,33 +2,42 @@ 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 := d.t.ExecuteTemplate(w, "404", nil); err != nil {
|
if err := 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 := d.t.ExecuteTemplate(w, "500", nil); err != nil {
|
if err := 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 := d.t.ExecuteTemplate(w, "tree", data); err != nil {
|
if err := t.ExecuteTemplate(w, "tree", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -36,23 +45,15 @@ 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
|
||||||
|
@ -61,6 +62,9 @@ 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.
|
||||||
|
@ -78,7 +82,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 := d.t.ExecuteTemplate(w, "file", data); err != nil {
|
if err := t.ExecuteTemplate(w, "file", data); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,10 @@
|
||||||
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 {
|
||||||
|
@ -24,13 +15,6 @@ 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 {
|
||||||
|
|
BIN
static/legit.png
BIN
static/legit.png
Binary file not shown.
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.3 KiB |
259
static/style.css
259
static/style.css
|
@ -1,18 +1,29 @@
|
||||||
|
: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: #000;
|
background: var(--light);
|
||||||
-webkit-text-size-adjust: none;
|
-webkit-text-size-adjust: none;
|
||||||
font-family: serif;
|
font-family: var(--sans-font);
|
||||||
color: #c9c9c9;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: monospace;
|
font-family: var(--mono-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: #1f9b92;
|
background: var(--medium-gray);
|
||||||
color: #000;
|
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,26 +34,20 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
max-width: 750px;
|
||||||
padding: 0 13px;
|
padding: 0 13px;
|
||||||
margin: 40px auto;
|
margin: 40px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main, footer {
|
||||||
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 {
|
||||||
|
@ -62,14 +67,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -95,22 +92,19 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #1f9b92;
|
color: var(--darker);
|
||||||
text-decoration: none;
|
border-bottom: 1.5px solid var(--medium-gray);
|
||||||
font-weight:bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #fff;
|
border-bottom: 1.5px solid var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
margin: 0px auto;
|
|
||||||
width: 80vw;
|
|
||||||
padding-top: 2em;
|
padding-top: 2em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: 6em 1fr minmax(0, 7em);
|
||||||
grid-gap: 0.5em;
|
grid-row-gap: 0.5em;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,83 +113,54 @@ a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.clone-url pre {
|
.clone-url pre {
|
||||||
color: #1f9b92;
|
color: var(--dark);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--gray);
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree {
|
.tree {
|
||||||
margin: 0px 10vw;
|
display: grid;
|
||||||
max-width: 80vw;
|
grid-template-columns: 8em minmax(0, 1fr);
|
||||||
}
|
grid-row-gap: 0.5em;
|
||||||
|
grid-column-gap: 1em;
|
||||||
.tree td {
|
min-width: 0;
|
||||||
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: 1fr auto;
|
grid-template-columns: 20rem minmax(0, 1fr);
|
||||||
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-left: 1ch;
|
padding-bottom: 1em;
|
||||||
border-left: solid 2px #797979;
|
border-bottom: 1.5px solid var(--medium-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-hash, .commit-email {
|
.log pre {
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log pre, .commit pre {
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode {
|
.mode {
|
||||||
font-family: monospace;
|
font-family: var(--mono-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
.readme pre {
|
.readme pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.readme * {
|
.readme {
|
||||||
margin: 0 auto;
|
background: var(--light-gray);
|
||||||
width: 80vw;
|
padding: 0.5rem;
|
||||||
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%;
|
||||||
|
@ -204,11 +169,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,14 +182,10 @@ 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 {
|
||||||
|
@ -243,75 +203,28 @@ a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-add {
|
.diff-add {
|
||||||
color: #1f9b92;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-del {
|
.diff-del {
|
||||||
color: #dc322f;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-noop {
|
.diff-noop {
|
||||||
}
|
color: var(--gray);
|
||||||
|
|
||||||
.refs {
|
|
||||||
width: 80vw;
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
font-family: serif;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-entry details pre, .commit-info details pre {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 1.0rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ref {
|
.ref {
|
||||||
font-family: monospace;
|
font-family: var(--display-font);
|
||||||
font-size: 75%;
|
font-size: 14px;
|
||||||
|
color: var(--gray);
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refs strong {
|
.refs strong {
|
||||||
|
@ -325,53 +238,29 @@ time {
|
||||||
-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: table;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-content {
|
|
||||||
color: #93a1a1;
|
|
||||||
background: #002b36;
|
|
||||||
overflow-y: hidden;
|
|
||||||
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;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
grid-template-columns: 1rem minmax(0, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: var(--light-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-content {
|
||||||
|
background: var(--light-gray);
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-type {
|
.diff-type {
|
||||||
color: #000;
|
color: var(--gray);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -399,13 +288,7 @@ time {
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
{{ define "commit" }}
|
{{ define "commit" }}
|
||||||
<html>
|
<html>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
<body>
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
<body>
|
||||||
{{ template "nav" . }}
|
{{ template "nav" . }}
|
||||||
<main>
|
<main>
|
||||||
<section class="commit">
|
<section class="commit">
|
||||||
|
@ -10,18 +11,13 @@
|
||||||
{{- .commit.Message -}}
|
{{- .commit.Message -}}
|
||||||
</pre>
|
</pre>
|
||||||
<div class="commit-info">
|
<div class="commit-info">
|
||||||
{{ .commit.Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .commit.Author.Email}}</a>
|
{{ .commit.Author.Name }} <span class="commit-email">{{ .commit.Author.Email}}</span>
|
||||||
<time>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
|
<div>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||||
{{ 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 }}" class="commit-hash">
|
<p><a href="/{{ .name }}/commit/{{ .commit.This }}">
|
||||||
{{ .commit.This }}
|
{{ .commit.This }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -30,7 +26,7 @@
|
||||||
{{ if .commit.Parent }}
|
{{ if .commit.Parent }}
|
||||||
<div>
|
<div>
|
||||||
<strong>parent</strong>
|
<strong>parent</strong>
|
||||||
<p><a href="/{{ .name }}/commit/{{ .commit.Parent }}" class="commit-hash">
|
<p><a href="/{{ .name }}/commit/{{ .commit.Parent }}">
|
||||||
{{ .commit.Parent }}
|
{{ .commit.Parent }}
|
||||||
</a></p>
|
</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,7 +99,6 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
{{ define "file" }}
|
{{ define "file" }}
|
||||||
<html>
|
<html>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
<body>
|
<title>{{.name }} — {{ .path }}</title>
|
||||||
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
<body>
|
||||||
{{ template "nav" . }}
|
{{ template "nav" . }}
|
||||||
<main>
|
<main>
|
||||||
<p>{{ .path }} (<a href="{{ .raw }}">raw</a>)</p>
|
<p>{{ .path }}</p>
|
||||||
<table class="file-wrapper">
|
<div class="file-wrapper">
|
||||||
<tbody><tr>
|
<div class="line-numbers">
|
||||||
<td class="line-numbers">
|
|
||||||
<pre>
|
|
||||||
{{- range .linecount }}
|
{{- range .linecount }}
|
||||||
<a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a>
|
<a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</pre>
|
</div>
|
||||||
</td>
|
<div class="file-content">
|
||||||
<td class="file-content">
|
<span></span>
|
||||||
<pre>
|
<pre>
|
||||||
{{- .content -}}
|
{{- .content -}}
|
||||||
</pre>
|
</pre>
|
||||||
</td>
|
</div>
|
||||||
</tbody></tr>
|
|
||||||
</table>
|
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{{ define "footer" }}
|
|
||||||
{{ if .CompiledFooter }}
|
|
||||||
<footer>
|
|
||||||
<span>{{ .CompiledFooter }}</span>
|
|
||||||
</footer>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
|
@ -3,28 +3,9 @@
|
||||||
<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 .parent }}
|
{{ if .servername }}
|
||||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .parent }}/</title>
|
|
||||||
|
|
||||||
{{ else if .path }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .path }}</title>
|
|
||||||
{{ else if .files }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }})</title>
|
|
||||||
{{ else if .commit }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .name }}: {{ .commit.This }}</title>
|
|
||||||
{{ else if .branches }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .name }}: refs</title>
|
|
||||||
{{ else if .commits }}
|
|
||||||
{{ if .log }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .name }}: log</title>
|
|
||||||
{{ else }}
|
|
||||||
<title>{{ .meta.Title }} — {{ .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 -->
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
{{ 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 }}
|
||||||
|
@ -15,8 +20,6 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
{{ define "log" }}
|
{{ define "log" }}
|
||||||
<html>
|
<html>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
<body>
|
|
||||||
|
<title>
|
||||||
|
{{ .name }} — log
|
||||||
|
</title>
|
||||||
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
<body>
|
||||||
{{ 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 }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
|
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}">{{ 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 }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
|
{{ .Author.Name }} <span class="commit-email">{{ .Author.Email }}</span>
|
||||||
<time>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
|
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -2,49 +2,41 @@
|
||||||
<html>
|
<html>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
|
|
||||||
<body>
|
<title>
|
||||||
|
{{ .name }} — refs
|
||||||
|
</title>
|
||||||
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
<body>
|
||||||
{{ template "nav" . }}
|
{{ template "nav" . }}
|
||||||
<main>
|
<main>
|
||||||
{{ $name := .name }}
|
{{ $name := .name }}
|
||||||
<h3>branches</h3>
|
<h3>branches</h3>
|
||||||
<div class="refs branches">
|
<div class="refs">
|
||||||
{{ range .branches }}
|
{{ range .branches }}
|
||||||
<div class="branch-entry">
|
<div>
|
||||||
<h4>{{ .Name.Short }}</h4>
|
<strong>{{ .Name.Short }}</strong>
|
||||||
<ul>
|
<a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a>
|
||||||
<li><a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a></li>
|
<a href="/{{ $name }}/log/{{ .Name.Short }}">log</a>
|
||||||
<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 tags">
|
<div class="refs">
|
||||||
{{ range .tags }}
|
{{ range .tags }}
|
||||||
<div class="tag-entry">
|
<div>
|
||||||
<h4>{{ .Name }}</h4>
|
<strong>{{ .Name }}</strong>
|
||||||
<time>{{ .Tagger.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
|
<a href="/{{ $name }}/tree/{{ .Name }}/">browse</a>
|
||||||
<ul>
|
<a href="/{{ $name }}/log/{{ .Name }}">log</a>
|
||||||
<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>
|
||||||
{{ end }}
|
|
||||||
{{ if .PGPSignature }}
|
|
||||||
<details><summary>PGP Signature</summary>
|
|
||||||
<pre>{{ .PGPSignature }}</pre>
|
|
||||||
</details>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
{{ define "repo" }}
|
{{ define "repo" }}
|
||||||
<html>
|
<html>
|
||||||
|
<title>{{ .name }}
|
||||||
|
{{ if .parent }}
|
||||||
|
— {{ .parent }}
|
||||||
|
{{ end }}
|
||||||
|
</title>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
<body>
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
|
||||||
|
<body>
|
||||||
{{ 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 }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
|
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}">{{ 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 }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
|
{{ .Author.Name }} <span class="commit-email">{{ .Author.Email }}</span>
|
||||||
<time>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</time>
|
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,8 +38,6 @@ git clone https://{{ .servername }}/{{ .name }}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,53 +1,44 @@
|
||||||
{{ define "tree" }}
|
{{ define "tree" }}
|
||||||
<html>
|
<html>
|
||||||
|
<title>{{ .name }}
|
||||||
|
{{ if .parent }}
|
||||||
|
— {{ .parent }}
|
||||||
|
{{ end }}
|
||||||
|
</title>
|
||||||
{{ template "head" . }}
|
{{ template "head" . }}
|
||||||
<body>
|
|
||||||
{{ template "repoheader" . }}
|
{{ template "repoheader" . }}
|
||||||
|
<body>
|
||||||
{{ template "nav" . }}
|
{{ template "nav" . }}
|
||||||
<main>
|
<main>
|
||||||
{{ $repo := .name }}
|
{{ $repo := .name }}
|
||||||
{{ $ref := .ref }}
|
{{ $ref := .ref }}
|
||||||
{{ $parent := .parent }}
|
{{ $parent := .parent }}
|
||||||
|
|
||||||
<table class="tree">
|
<div class="tree">
|
||||||
{{ if $parent }}
|
{{ if $parent }}
|
||||||
<tr>
|
<div></div>
|
||||||
<td></td>
|
<div><a href="../">..</a></div>
|
||||||
<td></td>
|
|
||||||
<td><a href="/{{ $repo }}/tree/{{ $ref }}/{{ .dotdot }}">..</a></td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ range .files }}
|
|
||||||
{{ if not .IsFile }}
|
|
||||||
<tr>
|
|
||||||
<td class="mode">{{ .Mode }}</td>
|
|
||||||
<td class="size">{{ .Size }}</td>
|
|
||||||
<td>
|
|
||||||
{{ if $parent }}
|
|
||||||
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>
|
|
||||||
{{ else }}
|
|
||||||
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a>
|
|
||||||
{{ end }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ range .files }}
|
{{ range .files }}
|
||||||
|
<div class="mode">{{ .Mode }}</div>
|
||||||
|
<div>
|
||||||
{{ if .IsFile }}
|
{{ if .IsFile }}
|
||||||
<tr>
|
|
||||||
<td class="mode">{{ .Mode }}</td>
|
|
||||||
<td class="size">{{ .Size }}</td>
|
|
||||||
<td>
|
|
||||||
{{ if $parent }}
|
{{ if $parent }}
|
||||||
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>
|
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a>
|
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</td>
|
{{ else }}
|
||||||
</tr>
|
{{ if $parent }}
|
||||||
|
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</table>
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
<article>
|
<article>
|
||||||
<pre>
|
<pre>
|
||||||
{{- if .readme }}{{ .readme }}{{- end -}}
|
{{- if .readme }}{{ .readme }}{{- end -}}
|
||||||
|
@ -55,6 +46,5 @@
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
{{ template "footer" .meta }}
|
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in a new issue