diff options
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | config.example.scfg | 6 | ||||
| -rw-r--r-- | go.mod | 43 | ||||
| -rw-r--r-- | go.sum | 140 | ||||
| -rw-r--r-- | pkg/config/config.go | 60 | ||||
| -rw-r--r-- | pkg/config/config_test.go | 38 | ||||
| -rw-r--r-- | pkg/ext/auth.go | 22 | ||||
| -rw-r--r-- | pkg/ext/compression.go | 31 | ||||
| -rw-r--r-- | pkg/ext/log.go | 4 | ||||
| -rw-r--r-- | pkg/ext/request.go | 14 | ||||
| -rw-r--r-- | pkg/ext/router.go | 24 | ||||
| -rw-r--r-- | pkg/git/git.go | 101 | ||||
| -rw-r--r-- | pkg/handler/about/handler.go | 3 | ||||
| -rw-r--r-- | pkg/handler/auth/login.go | 4 | ||||
| -rw-r--r-- | pkg/handler/git/handler.go | 69 | ||||
| -rw-r--r-- | pkg/handler/router.go | 18 | ||||
| -rw-r--r-- | pkg/handler/static/handler.go | 63 | ||||
| -rw-r--r-- | pkg/service/git.go | 29 | ||||
| m--------- | scss/bootstrap | 0 | ||||
| -rw-r--r-- | scss/main.scss | 22 | ||||
| -rw-r--r-- | scss/tree.scss | 399 | ||||
| -rw-r--r-- | templates/base.qtpl | 28 | ||||
| -rw-r--r-- | templates/base.qtpl.go | 131 | ||||
| -rw-r--r-- | templates/gititemblob.qtpl | 4 | ||||
| -rw-r--r-- | templates/gititemblob.qtpl.go | 4 | ||||
| -rw-r--r-- | templates/gititemsummary.qtpl | 15 | ||||
| -rw-r--r-- | templates/gititemsummary.qtpl.go | 150 | ||||
| -rw-r--r-- | templates/gititemtree.qtpl | 12 | ||||
| -rw-r--r-- | templates/gititemtree.qtpl.go | 12 |
29 files changed, 1152 insertions, 302 deletions
@@ -59,14 +59,12 @@ My email: mail@gabrielgio.me -### Missing for 1.0 +### TODO -- Git clone support: that is very important feature for a git forge. -- Dark mode support: in the same way as mobile support this should be supported - from the get go. I have slacked on this one but it should be done before 1.0. -- Some rework on summary page: it is feels a bit crude as it is. +- dark theme that does not require js ### Ideas + - Code snippet support - Setup manual (man feature perhaps?) - Add metrics diff --git a/config.example.scfg b/config.example.scfg index 2ed13cd..e726c19 100644 --- a/config.example.scfg +++ b/config.example.scfg @@ -3,7 +3,11 @@ listen-addr unix://var/run/cerrado.sock root-readme /srv/git/README.md -syntax-highlight monokailight +syntax-highlight monokailight monokai + +# full hostname address plus protocol. +# This is going to be used to display full link (e.g.: clone link) +hostname https://domain.tld # if passphrase is empty the whole authentication, private/public repository # and login UI will be disabled, and only public repository will be displayed. @@ -1,39 +1,42 @@ module git.gabrielgio.me/cerrado -go 1.22.2 +go 1.24.0 + +toolchain go1.24.9 require ( git.sr.ht/~emersion/go-scfg v0.0.0-20250102010123-2f3fb2d5d50e - github.com/alecthomas/chroma/v2 v2.15.0 - github.com/andybalholm/brotli v1.1.1 - github.com/go-git/go-git/v5 v5.13.2 - github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e - github.com/google/go-cmp v0.6.0 - github.com/klauspost/compress v1.17.11 + github.com/alecthomas/chroma/v2 v2.20.0 + github.com/andybalholm/brotli v1.2.0 + github.com/go-git/go-git/v5 v5.16.3 + github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a + github.com/google/go-cmp v0.7.0 + github.com/klauspost/compress v1.18.1 github.com/valyala/quicktemplate v1.8.0 - golang.org/x/crypto v0.33.0 - golang.org/x/sync v0.11.0 + golang.org/x/crypto v0.43.0 + golang.org/x/sync v0.17.0 ) require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/cloudflare/circl v1.6.0 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cyphar/filepath-securejoin v0.5.0 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) @@ -1,80 +1,63 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 h1:9Udx5fm4vRtmgDIBjy2ef5QioHbzpw5oHabbhpAUyEw= -git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= git.sr.ht/~emersion/go-scfg v0.0.0-20250102010123-2f3fb2d5d50e h1:dKdXdn8yhldnz8ynz/HNmLPk40tvTwDBsTdQcHddesg= git.sr.ht/~emersion/go-scfg v0.0.0-20250102010123-2f3fb2d5d50e/go.mod h1:7eWdbNLaP10M+QMSmmHYmYd8q86NHZy12Cq9TiZ9LC8= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= -github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= -github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= +github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= +github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= +github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= -github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cyphar/filepath-securejoin v0.3.5 h1:L81NHjquoQmcPgXcttUS9qTSR/+bXry6pbSINQGpjj4= -github.com/cyphar/filepath-securejoin v0.3.5/go.mod h1:edhVd3c6OXKjUmSrVa/tGJRS9joFTxlslFCAyaxigkE= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= +github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= -github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= -github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= -github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= -github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g= -github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ= -github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A= +github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -84,29 +67,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdqCysW5k= @@ -116,39 +94,29 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 9dbf449..ff969ec 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -39,6 +39,11 @@ type ( Public bool } + SyntaxHighlight struct { + Dark string + Light string + } + // configuration represents file configuration. // fields needs to be exported to cmp to work configuration struct { @@ -50,7 +55,8 @@ type ( Repositories []*GitRepositoryConfiguration RootReadme string Scans []*scan - SyntaxHighlight string + SyntaxHighlight SyntaxHighlight + Hostname string } // This is a per repository configuration. @@ -74,7 +80,8 @@ type ( removeSuffix bool repositories []*GitRepositoryConfiguration rootReadme string - syntaxHighlight string + syntaxHighlight SyntaxHighlight + hostname string } ) @@ -95,6 +102,7 @@ func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, e passphrase: []byte(config.Passphrase), repositories: config.Repositories, rootReadme: config.RootReadme, + hostname: config.Hostname, syntaxHighlight: config.SyntaxHighlight, removeSuffix: config.RemoveSuffix, orderBy: parseOrderBy(config.OrderBy), @@ -117,12 +125,20 @@ func (c *ConfigurationRepository) GetRootReadme() string { return c.rootReadme } +func (c *ConfigurationRepository) GetHostname() string { + return c.hostname +} + func (c *ConfigurationRepository) GetOrderBy() OrderBy { return c.orderBy } func (c *ConfigurationRepository) GetSyntaxHighlight() string { - return c.syntaxHighlight + return c.syntaxHighlight.Light +} + +func (c *ConfigurationRepository) GetSyntaxHighlightDark() string { + return c.syntaxHighlight.Dark } func (c *ConfigurationRepository) GetListenAddr() string { @@ -219,6 +235,11 @@ func parse(r io.Reader) (*configuration, error) { return nil, err } + err = setHostname(block, &config.Hostname) + if err != nil { + return nil, err + } + err = setListenAddr(block, &config.ListenAddr) if err != nil { return nil, err @@ -311,11 +332,16 @@ func defaultConfiguration() *configuration { return &configuration{ Scans: defaultScans(), RootReadme: "", + Hostname: defaultHostname(), ListenAddr: defaultAddr(), Repositories: make([]*GitRepositoryConfiguration, 0), } } +func defaultHostname() string { + return "https://localhost:8080" +} + func defaultScans() []*scan { return []*scan{} } @@ -339,6 +365,11 @@ func setRootReadme(block scfg.Block, readme *string) error { return setString(scanDir, readme) } +func setHostname(block scfg.Block, hostname *string) error { + scanDir := block.Get("hostname") + return setString(scanDir, hostname) +} + func setPassphrase(block scfg.Block, listenAddr *string) error { scanDir := block.Get("passphrase") return setString(scanDir, listenAddr) @@ -349,9 +380,26 @@ func setAESKey(block scfg.Block, listenAddr *string) error { return setString(scanDir, listenAddr) } -func setSyntaxHighlight(block scfg.Block, listenAddr *string) error { - scanDir := block.Get("syntax-highlight") - return setString(scanDir, listenAddr) +func setSyntaxHighlight(block scfg.Block, sh *SyntaxHighlight) error { + shDir := block.Get("syntax-highlight") + if shDir == nil { + return nil + } + + themes := shDir.Params + if len(themes) > 2 || len(themes) == 0 { + return errors.New("syntax-highlight must contains at most two params and at least one, light then dark theme name") + } + + sh.Light = themes[0] + if len(themes) > 1 { + sh.Dark = themes[1] + } else { + // if dark is not set use light + sh.Dark = sh.Light + } + + return nil } func setOrderby(block scfg.Block, orderBy *string) error { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 949238e..50744b5 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -25,6 +25,7 @@ func TestFileParsing(t *testing.T) { }, }, ListenAddr: defaultAddr(), + Hostname: defaultHostname(), Repositories: []*GitRepositoryConfiguration{}, }, }, @@ -42,15 +43,32 @@ scan "/srv/git" { }, }, ListenAddr: defaultAddr(), + Hostname: defaultHostname(), Repositories: []*GitRepositoryConfiguration{}, }, }, { + name: "themes", + config: ` +syntax-highlight light dark`, + expectedConfig: &configuration{ + Scans: defaultScans(), + ListenAddr: defaultAddr(), + Hostname: defaultHostname(), + Repositories: []*GitRepositoryConfiguration{}, + SyntaxHighlight: SyntaxHighlight{ + Light: "light", + Dark: "dark", + }, + }, + }, + { name: "minimal repository", config: `repository /srv/git/cerrado.git`, expectedConfig: &configuration{ Scans: defaultScans(), ListenAddr: defaultAddr(), + Hostname: defaultHostname(), Repositories: []*GitRepositoryConfiguration{ { Name: "cerrado.git", @@ -74,6 +92,7 @@ repository /srv/git/cerrado.git { expectedConfig: &configuration{ Scans: defaultScans(), ListenAddr: defaultAddr(), + Hostname: defaultHostname(), Repositories: []*GitRepositoryConfiguration{ { Name: "cerrado", @@ -91,6 +110,7 @@ repository /srv/git/cerrado.git { expectedConfig: &configuration{ Scans: defaultScans(), ListenAddr: defaultAddr(), + Hostname: defaultHostname(), Repositories: []*GitRepositoryConfiguration{}, }, }, @@ -99,6 +119,7 @@ repository /srv/git/cerrado.git { config: `listen-addr unix://var/run/cerrado/cerrado.sock`, expectedConfig: &configuration{ Scans: defaultScans(), + Hostname: defaultHostname(), ListenAddr: "unix://var/run/cerrado/cerrado.sock", Repositories: []*GitRepositoryConfiguration{}, }, @@ -112,6 +133,7 @@ aes-key 8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBU syntax-highlight monokailight order-by lastcommit-desc remove-suffix true +hostname https://domain.tld scan "/srv/git" { public true @@ -132,12 +154,16 @@ repository /srv/git/cerrado.git { Path: "/srv/git", }, }, - ListenAddr: "unix://var/run/cerrado/cerrado.sock", - Passphrase: "$2a$14$VnB/ZcB1DUDkMnosRA6Y7.dj8h5eroslDxTeXlLwfQX/x86mh6WAq", - AESKey: "8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBUfiR8ES46ZLLb4w==", - SyntaxHighlight: "monokailight", - OrderBy: "lastcommit-desc", - RemoveSuffix: true, + ListenAddr: "unix://var/run/cerrado/cerrado.sock", + Passphrase: "$2a$14$VnB/ZcB1DUDkMnosRA6Y7.dj8h5eroslDxTeXlLwfQX/x86mh6WAq", + AESKey: "8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBUfiR8ES46ZLLb4w==", + SyntaxHighlight: SyntaxHighlight{ + Light: "monokailight", + Dark: "monokailight", + }, + OrderBy: "lastcommit-desc", + RemoveSuffix: true, + Hostname: "https://domain.tld", Repositories: []*GitRepositoryConfiguration{ { Name: "linux.git", diff --git a/pkg/ext/auth.go b/pkg/ext/auth.go index 5c3070e..ef126ec 100644 --- a/pkg/ext/auth.go +++ b/pkg/ext/auth.go @@ -14,19 +14,20 @@ type authService interface { ValidateToken(token []byte) (bool, error) } -func DisableAuthentication(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func DisableAuthentication(next HandlerFunc) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { ctx := r.Context() ctx = context.WithValue(ctx, "disableAuthentication", true) - next(w, r.WithContext(ctx)) + r.Request = r.WithContext(ctx) + next(w, r) } } func VerifyRespository( config *serverconfig.ConfigurationRepository, -) func(next http.HandlerFunc) http.HandlerFunc { - return func(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +) func(next HandlerFunc) HandlerFunc { + return func(next HandlerFunc) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { name := r.PathValue("name") if name != "" { repo := config.GetByName(name) @@ -41,9 +42,9 @@ func VerifyRespository( } } -func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc { - return func(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func Authenticate(auth authService) func(next HandlerFunc) HandlerFunc { + return func(next HandlerFunc) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { cookie, err := r.Cookie("auth") if err != nil { if !errors.Is(err, http.ErrNoCookie) { @@ -70,9 +71,10 @@ func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc ctx := r.Context() ctx = context.WithValue(ctx, "logged", valid) + r.Request = r.WithContext(ctx) slog.Info("Validated token", "valid?", valid) - next(w, r.WithContext(ctx)) + next(w, r) } } } diff --git a/pkg/ext/compression.go b/pkg/ext/compression.go index 6c7a219..d3a3df1 100644 --- a/pkg/ext/compression.go +++ b/pkg/ext/compression.go @@ -15,18 +15,37 @@ import ( "github.com/klauspost/compress/zstd" ) -var ( - errInvalidParam = errors.New("Invalid weighted param") -) +var errInvalidParam = errors.New("Invalid weighted param") type CompressionResponseWriter struct { innerWriter http.ResponseWriter compressWriter io.Writer } -func Compress(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func Compress(next HandlerFunc) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { + // TODO: hand this better + if strings.HasSuffix(r.URL.Path, ".tar.gz") { + next(w, r) + return + } + + if accept, ok := r.Header["Accept-Encoding"]; ok { + if compress, algo := GetCompressionWriter(u.FirstOrZero(accept), w); algo != "" { + defer compress.Close() + w.Header().Add("Content-Encoding", algo) + w = &CompressionResponseWriter{ + innerWriter: w, + compressWriter: compress, + } + } + } + next(w, r) + } +} +func Decompress(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { // TODO: hand this better if strings.HasSuffix(r.URL.Path, ".tar.gz") { next(w, r) @@ -61,12 +80,12 @@ func GetCompressionWriter(header string, inner io.Writer) (io.WriteCloser, strin default: return nil, "" } - } func (c *CompressionResponseWriter) Header() http.Header { return c.innerWriter.Header() } + func (c *CompressionResponseWriter) Write(b []byte) (int, error) { return c.compressWriter.Write(b) } diff --git a/pkg/ext/log.go b/pkg/ext/log.go index 8e68134..e0ad89f 100644 --- a/pkg/ext/log.go +++ b/pkg/ext/log.go @@ -39,8 +39,8 @@ func wrap(w http.ResponseWriter) *statusWraper { } } -func Log(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func Log(next HandlerFunc) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { t := time.Now() s := wrap(w) next(s, r) diff --git a/pkg/ext/request.go b/pkg/ext/request.go new file mode 100644 index 0000000..d1593b2 --- /dev/null +++ b/pkg/ext/request.go @@ -0,0 +1,14 @@ +package ext + +import ( + "io" + "net/http" +) + +type Request struct { + *http.Request +} + +func (r *Request) ReadBody() io.ReadCloser { + return r.Body +} diff --git a/pkg/ext/router.go b/pkg/ext/router.go index ce4c126..bbbffa1 100644 --- a/pkg/ext/router.go +++ b/pkg/ext/router.go @@ -16,8 +16,9 @@ type ( middlewares []Middleware router *http.ServeMux } - Middleware func(next http.HandlerFunc) http.HandlerFunc - ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error + HandlerFunc func(http.ResponseWriter, *Request) + Middleware func(next HandlerFunc) HandlerFunc + ErrorRequestHandler func(w http.ResponseWriter, r *Request) error ) func NewRouter() *Router { @@ -34,15 +35,15 @@ func (r *Router) AddMiddleware(middleware Middleware) { r.middlewares = append(r.middlewares, middleware) } -func wrapError(next ErrorRequestHandler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func wrapError(next ErrorRequestHandler) HandlerFunc { + return func(w http.ResponseWriter, r *Request) { if err := next(w, r); err != nil { if errors.Is(err, service.ErrRepositoryNotFound) || errors.Is(err, plumbing.ErrReferenceNotFound) { NotFound(w, r) } else { slog.Error("Internal Server Error", "error", err) - InternalServerError(r, w, err) + InternalServerError(w, r, err) } } } @@ -54,7 +55,7 @@ func (r *Router) run(next ErrorRequestHandler) http.HandlerFunc { for _, r := range r.middlewares { req = r(req) } - req(w, re) + req(w, &Request{Request: re}) } } @@ -62,19 +63,26 @@ func (r *Router) HandleFunc(path string, handler ErrorRequestHandler) { r.router.HandleFunc(path, r.run(handler)) } -func NotFound(w http.ResponseWriter, r *http.Request) { +func NotFound(w http.ResponseWriter, r *Request) { w.WriteHeader(http.StatusNotFound) templates.WritePageTemplate(w, &templates.ErrorPage{ Message: "Not Found", }, r.Context()) } +func BadRequest(w http.ResponseWriter, r *Request, msg string) { + w.WriteHeader(http.StatusBadRequest) + templates.WritePageTemplate(w, &templates.ErrorPage{ + Message: msg, + }, r.Context()) +} + func Redirect(w http.ResponseWriter, location string) { w.Header().Add("location", location) w.WriteHeader(http.StatusTemporaryRedirect) } -func InternalServerError(r *http.Request, w http.ResponseWriter, err error) { +func InternalServerError(w http.ResponseWriter, r *Request, err error) { w.WriteHeader(http.StatusInternalServerError) templates.WritePageTemplate(w, &templates.ErrorPage{ Message: fmt.Sprintf("Internal Server Error:\n%s", err.Error()), diff --git a/pkg/git/git.go b/pkg/git/git.go index 64c721a..83f3f93 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -3,10 +3,13 @@ package git import ( "archive/tar" "bytes" + "context" "errors" "fmt" "io" "io/fs" + "log/slog" + "os/exec" "path" "sort" "time" @@ -432,6 +435,68 @@ func (g *GitRepository) FileContent(path string) ([]byte, error) { return buf.Bytes(), nil } +func (g *GitRepository) WriteInfoRefs(ctx context.Context, w io.Writer) error { + cmd := exec.CommandContext( + ctx, + "git-upload-pack", + "--stateless-rpc", + "--advertise-refs", + ".", + ) + + cmd.Dir = g.path + cmd.Env = []string{ + // TODO: get this from header. + "GIT_PROTOCOL=version=2", + } + + var errBuff bytes.Buffer + cmd.Stderr = &errBuff + cmd.Stdout = w + + err := packLine(w, "# service=git-upload-pack\n") + if err != nil { + return err + } + + err = packFlush(w) + if err != nil { + return err + } + + err = cmd.Run() + if err != nil { + slog.Error("Error upload pack refs", "message", errBuff.String()) + return err + } + return nil +} + +func (g *GitRepository) WriteUploadPack(ctx context.Context, r io.Reader, w io.Writer) error { + cmd := exec.CommandContext( + ctx, + "git-upload-pack", + "--stateless-rpc", + ".", + ) + cmd.Dir = g.Path() + cmd.Env = []string{ + // TODO: get this from header. + "GIT_PROTOCOL=version=2", + } + var errBuff bytes.Buffer + cmd.Stderr = &errBuff + cmd.Stdout = w + cmd.Stdin = r + + if err := cmd.Run(); err != nil { + slog.ErrorContext(ctx, "Git upload pack failed", "error", err, "message", errBuff.String()) + return err + } + + return nil +} + func (g *GitRepository) WriteTar(w io.Writer, prefix string) error { tw := tar.NewWriter(w) defer tw.Close() @@ -613,3 +678,39 @@ func (self *tagList) Less(i, j int) bool { return dateI.After(dateJ) } + +func packLine(w io.Writer, s string) error { + _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) + return err +} + +func packFlush(w io.Writer) error { + _, err := fmt.Fprint(w, "0000") + return err +} + +type debugReader struct { + r io.Reader +} + +func (d *debugReader) Read(p []byte) (n int, err error) { + r, err := d.r.Read(p) + if err != nil { + if errors.Is(io.EOF, err) { + fmt.Printf("READ: EOF\n") + } + return r, err + } + + fmt.Printf("READ: %s\n", p[:r]) + return r, nil +} + +type debugWriter struct { + w io.Writer +} + +func (d *debugWriter) Write(p []byte) (n int, err error) { + fmt.Printf("WRITE: %s\n", p) + return d.w.Write(p) +} diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go index ee084cd..b3a1593 100644 --- a/pkg/handler/about/handler.go +++ b/pkg/handler/about/handler.go @@ -9,6 +9,7 @@ import ( "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" + "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/templates" ) @@ -26,7 +27,7 @@ func NewAboutHandler(configRepo configurationRepository) *AboutHandler { return &AboutHandler{configRepo.GetRootReadme()} } -func (g *AboutHandler) About(w http.ResponseWriter, r *http.Request) error { +func (g *AboutHandler) About(w http.ResponseWriter, r *ext.Request) error { f, err := os.Open(g.readmePath) if err != nil { return err diff --git a/pkg/handler/auth/login.go b/pkg/handler/auth/login.go index 89fd87b..9cc13cc 100644 --- a/pkg/handler/auth/login.go +++ b/pkg/handler/auth/login.go @@ -26,7 +26,7 @@ func NewLoginHandler(auth authService) *LoginHandler { } } -func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error { +func (g *LoginHandler) Logout(w http.ResponseWriter, r *ext.Request) error { cookie := &http.Cookie{ Name: "auth", Value: "", @@ -44,7 +44,7 @@ func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *LoginHandler) Login(w http.ResponseWriter, r *http.Request) error { +func (g *LoginHandler) Login(w http.ResponseWriter, r *ext.Request) error { referer := r.URL.Query().Get("referer") // if query value is empty tries to get from header diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go index a9be54c..d046d19 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -2,6 +2,7 @@ package git import ( "bytes" + "compress/gzip" "errors" "fmt" "io" @@ -37,6 +38,7 @@ type ( GetRootReadme() string GetSyntaxHighlight() string GetOrderBy() config.OrderBy + GetHostname() string } ) @@ -47,7 +49,7 @@ func NewGitHandler(gitService *service.GitService, confRepo configurationReposit } } -func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) List(w http.ResponseWriter, r *ext.Request) error { // this is the only handler that needs to handle authentication itself. // everything else relay on name path parameter logged := ext.IsLoggedIn(r.Context()) @@ -89,7 +91,7 @@ func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Archive(w http.ResponseWriter, r *ext.Request) error { ext.SetGZip(w) name := r.PathValue("name") file := r.PathValue("file") @@ -115,7 +117,51 @@ func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Multiplex(w http.ResponseWriter, r *ext.Request) error { + path := r.PathValue("rest") + name := r.PathValue("name") + + if r.URL.RawQuery == "service=git-receive-pack" { + ext.BadRequest(w, r, "no pushing allowed") + return nil + } + + if path == "info/refs" && r.URL.RawQuery == "service=git-upload-pack" && r.Method == "GET" { + w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") + + err := g.gitService.WriteInfoRefs(r.Context(), name, w) + if err != nil { + slog.Error("Error WriteInfoRefs", "error", err) + } + } else if path == "git-upload-pack" && r.Method == "POST" { + w.Header().Set("content-type", "application/x-git-upload-pack-result") + w.Header().Set("Connection", "Keep-Alive") + w.Header().Set("Transfer-Encoding", "chunked") + w.WriteHeader(http.StatusOK) + + reader := r.Body + + if r.Header.Get("Content-Encoding") == "gzip" { + var err error + reader, err = gzip.NewReader(r.Body) + if err != nil { + return err + } + defer reader.Close() + } + + err := g.gitService.WriteUploadPack(r.Context(), name, reader, w) + if err != nil { + slog.Error("Error WriteUploadPack", "error", err) + } + } else if r.Method == "GET" { + return g.Summary(w, r) + } + + return nil +} + +func (g *GitHandler) Summary(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) @@ -149,13 +195,14 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { Tags: tags, Branches: branches, Commits: commits, + Hostname: g.config.GetHostname(), }, } templates.WritePageTemplate(w, gitList, r.Context()) return nil } -func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) About(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) @@ -199,7 +246,7 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Refs(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") @@ -230,7 +277,7 @@ func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Tree(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -259,7 +306,7 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Blob(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -302,6 +349,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { formatter := html.New( html.WithLineNumbers(true), html.WithLinkableLineNumbers(true, "L"), + html.WithClasses(true), ) iterator, err := lexer.Tokenise(nil, string(file)) @@ -327,7 +375,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Log(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -350,7 +398,7 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Ref(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Ref(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -372,7 +420,7 @@ func (g *GitHandler) Ref(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Commit(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -393,6 +441,7 @@ func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error { formatter := html.New( html.WithLineNumbers(true), html.WithLinkableLineNumbers(true, "L"), + html.WithClasses(true), ) iterator, err := lexer.Tokenise(nil, diff) diff --git a/pkg/handler/router.go b/pkg/handler/router.go index e461922..1fbc4e3 100644 --- a/pkg/handler/router.go +++ b/pkg/handler/router.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" serverconfig "git.gabrielgio.me/cerrado/pkg/config" @@ -10,6 +11,7 @@ import ( "git.gabrielgio.me/cerrado/pkg/handler/git" "git.gabrielgio.me/cerrado/pkg/handler/static" "git.gabrielgio.me/cerrado/pkg/service" + "git.gabrielgio.me/cerrado/templates" ) // Mount handler gets the requires service and repository to build the handlers @@ -31,6 +33,14 @@ func MountHandler( return nil, err } + cssStaticHandler, err := static.ServeStaticCSSHandler( + configRepo.GetSyntaxHighlight(), + configRepo.GetSyntaxHighlightDark(), + ) + if err != nil { + return nil, err + } + mux := ext.NewRouter() mux.AddMiddleware(ext.Compress) mux.AddMiddleware(ext.Log) @@ -45,8 +55,14 @@ func MountHandler( } mux.HandleFunc("/static/{file}", staticHandler) + // add slug and session so css file can be cached forever. + // Slug follow commit id, which is update every new version + // Session is update every time server restarts, this allows the css to be + // cached forever but refresh if the admin updates the server configuration. + mux.HandleFunc(fmt.Sprintf("/static/theme.%s%s.css", templates.Session, templates.Slug), cssStaticHandler) mux.HandleFunc("/{name}/about/{$}", gitHandler.About) - mux.HandleFunc("/{name}/", gitHandler.Summary) + mux.HandleFunc("/{name}", gitHandler.Multiplex) + mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex) mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs) mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree) mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob) diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go index 361f690..6cc884e 100644 --- a/pkg/handler/static/handler.go +++ b/pkg/handler/static/handler.go @@ -1,6 +1,9 @@ package static import ( + "bytes" + "fmt" + "io" "io/fs" "mime" "net/http" @@ -8,6 +11,9 @@ import ( "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/static" + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/styles" ) func ServeStaticHandler() (ext.ErrorRequestHandler, error) { @@ -16,7 +22,7 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) { return nil, err } - return func(w http.ResponseWriter, r *http.Request) error { + return func(w http.ResponseWriter, r *ext.Request) error { var ( f = r.PathValue("file") e = filepath.Ext(f) @@ -24,7 +30,60 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) { ) ext.SetMIME(w, m) w.Header().Add("Cache-Control", "max-age=31536000") - http.ServeFileFS(w, r, staticFs, f) + http.ServeFileFS(w, r.Request, staticFs, f) return nil }, nil } + +func ServeStaticCSSHandler(lightTheme, darkTheme string) (ext.ErrorRequestHandler, error) { + var ( + lightStyle = styles.Get(lightTheme) + darkStyle = styles.Get(darkTheme) + formatter = html.New( + html.WithCSSComments(false), + ) + ) + + return func(w http.ResponseWriter, r *ext.Request) error { + ext.SetMIME(w, "text/css") + w.Header().Add("Cache-Control", "max-age=31536000") + + // use buffer so this function can fail before writing to http.ResponseWriter + var buffer bytes.Buffer + + var style *chroma.Style + style = darkStyle + buffer.Write([]byte("[data-bs-theme=\"dark\"] {\n")) + err := formatter.WriteCSS(&ws{&buffer}, style) + if err != nil { + return err + } + buffer.Write([]byte("}\n")) + + style = lightStyle + buffer.Write([]byte("[data-bs-theme=\"light\"] {\n")) + err = formatter.WriteCSS(&ws{&buffer}, style) + if err != nil { + return err + } + buffer.Write([]byte("}")) + + _, err = io.Copy(w, &buffer) + if err != nil { + return err + } + + return nil + }, nil +} + +type ws struct { + inner io.Writer +} + +// This is very cursed, and rely on the fact that it writes every css rule at time. +// it adds & to the begging so it can be nested by the ServeStaticCSSHandler. +// This will allow the follow bootstrap data-bs-theme. +func (w *ws) Write(p []byte) (n int, err error) { + return fmt.Fprintf(w.inner, "& %s", string(p)) +} diff --git a/pkg/service/git.go b/pkg/service/git.go index 5410d7a..6aa5cd6 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -2,6 +2,7 @@ package service import ( "compress/gzip" + "context" "errors" "io" "log/slog" @@ -299,3 +300,31 @@ func (g *GitService) GetHead(name string) (*plumbing.Reference, error) { return repo.Head() } + +func (g *GitService) WriteInfoRefs(ctx context.Context, name string, w io.Writer) error { + r := g.configRepo.GetByName(name) + if r == nil { + return ErrRepositoryNotFound + } + + repo, err := git.OpenRepository(r.Path) + if err != nil { + return err + } + + return repo.WriteInfoRefs(ctx, w) +} + +func (g *GitService) WriteUploadPack(ctx context.Context, name string, re io.Reader, w io.Writer) error { + r := g.configRepo.GetByName(name) + if r == nil { + return ErrRepositoryNotFound + } + + repo, err := git.OpenRepository(r.Path) + if err != nil { + return err + } + + return repo.WriteUploadPack(ctx, re, w) +} diff --git a/scss/bootstrap b/scss/bootstrap -Subproject 6e1f75f420f68e1d52733b8e407fc7c3766c9db +Subproject 25aa8cc0b32f0d1a54be575347e6d84b70b1acd diff --git a/scss/main.scss b/scss/main.scss index e0fecf1..a98c0a2 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -12,7 +12,7 @@ $btn-border-radius: 0; // basic functionality @import "bootstrap/scss/_functions.scss"; @import "bootstrap/scss/_variables.scss"; -@import "bootstrap/scss/_variables-dark.scss"; +//@import "bootstrap/scss/_variables-dark.scss"; @import "bootstrap/scss/_maps.scss"; @import "bootstrap/scss/_mixins.scss"; @import "bootstrap/scss/_utilities.scss"; @@ -27,6 +27,7 @@ $navbar-nav-link-padding-x: $spacer; @import "bootstrap/scss/_grid.scss"; @import "bootstrap/scss/_forms.scss"; @import "bootstrap/scss/_buttons.scss"; +@import "bootstrap/scss/mixins/_color-mode.scss"; @import "tree.scss"; // overwrite to reduce the ammount of css generated by loading all utilities @@ -80,6 +81,12 @@ body { margin: 0; } +@include color-mode(dark) { + body { + background: #212529; + } +} + // prevert wierd input overflowing 100% input { width: 100%; @@ -116,6 +123,12 @@ a[href]:not([href=""]):not(.nav-link) { background: #f8f9fa; } +@include color-mode(dark) { + .event { + background: #131618; + } +} + .event-commit { background: #dadada; padding: 5px; @@ -136,6 +149,13 @@ a[href]:not([href=""]):not(.nav-link) { } +@include color-mode(dark) { + .event-commit { + background: #000; + } +} + + .selected { text-decoration: underline; } diff --git a/scss/tree.scss b/scss/tree.scss index 05828dc..58258bd 100644 --- a/scss/tree.scss +++ b/scss/tree.scss @@ -53,6 +53,20 @@ color: $gray-700; } + @media(prefers-color-scheme: dark) { + .name.blob a { + color: inherit; + } + + .mode, + .commit, + .commit a, + .date, + .size { + color: inherit; + } + } + .name.blob { text-overflow: ellipsis; white-space: nowrap; @@ -85,7 +99,392 @@ // Striped rows &:nth-child(10n+#{$i}) { background: rgba(0, 0, 0, .05); + + @media(prefers-color-scheme: dark) { + background: lighten($gray-900, 5); + } + } + } + } +} + +.code-view { + display: grid; + grid-template-columns: auto auto auto 1fr; + grid-template-rows: auto; + + .blame-user { + grid-column-start: 1; + grid-row-start: 1; + background: #ddd; + + @media(prefers-color-scheme: dark) { + background: $gray-900; + } + + .hunk { + padding-left: 0.5rem; + } + } + + .blame-time { + grid-column-start: 2; + grid-row-start: 1; + background: #ddd; + border-right: 1px solid #444; + text-align: right; + + @media(prefers-color-scheme: dark) { + background: $gray-900; + } + + .hunk { + padding-right: 0.5rem; + } + } + + .hunk:nth-child(2n) { + background: #eee; + + @media(prefers-color-scheme: dark) { + background: lighten($gray-900, 5); + } + } + + .lines { + grid-column-start: 3; + grid-row-start: 1; + text-align: right; + padding-left: 0.5rem; + padding-right: 0.5rem; + + background: #eee; + border-right: 1px solid #444; + + @media(prefers-color-scheme: dark) { + background: lighten($gray-900, 5); + } + + a:target::before, + a.selected::before { + display: block; + content: ""; + // +6px to connect multiple selected lines + height: calc(1rem + 6px); + width: 100%; + z-index: -1; + position: absolute; + left: 0; + background: lighten($blue, 35); + + @media(prefers-color-scheme: dark) { + background: $black; + } + } + } + + .highlight { + grid-column-start: 4; + grid-row-start: 1; + padding-left: 1rem; + background: transparent; + overflow-x: hidden; + + pre { + background: transparent; + } + } + + .ruler { + background: transparent; + grid-column-start: 4; + grid-row-start: 1; + display: block; + padding-left: calc(1rem + 4px); + height: 100%; + pointer-events: none; + overflow-x: hidden; + + pre { + background: transparent; + } + + &>span { + height: 100%; + display: inline-block; + border-right: 1px solid $gray-200; + + @media(prefers-color-scheme: dark) { + border-right: 1px solid #343a40; } } } } + +.ref { + border-width: 1px; + border-style: solid; + padding: 0.1rem 0.2rem; + + &.branch { + border-color: darken($info, 20); + background: $info; + color: $white !important; + } + + &.tag { + border-color: darken($primary, 20); + background: $primary; + color: $white; + } + + &.tag.annotated { + border-color: darken($success, 20); + background: $success; + color: $white; + } +} + +.diff { + .lineno { + text-decoration: none; + } + + .text-success { + color: color_adjust_contrast_AERT(darken($success, 10), white) !important; + } + + .text-danger { + color: color_adjust_contrast_AERT(darken($danger, 10), white) !important; + } + + pre { + background: transparent; + } + + @media(prefers-color-scheme: dark) { + $success-dark: #2bb34b; + $danger-dark: #ff3e3e; + + .text-success { + color: $success-dark !important; + } + + .text-danger { + color: $danger-dark !important; + } + } +} + +img { + max-width: 100%; +} + +.prepare-patchset { + legend { + font-weight: bold; + } + + label { + margin-right: 1rem; + cursor: pointer; + } + + details { + display: inline; + color: $gray-600; + + &[open] { + display: block; + color: $black; + + summary { + color: $black; + } + } + + ul { + list-style: none; + padding-left: 0; + } + + li { + margin-top: 1rem; + } + + @media(prefers-color-scheme: dark) { + color: $gray-500; + + &[open] { + color: $gray-100; + + summary { + color: $gray-100; + } + } + } + } + + .event-list { + display: flex; + flex-direction: column; + + &.reverse { + flex-direction: column-reverse; + } + + input[type="radio"] { + display: none; + } + + &>.commit-diff { + margin-top: 1rem; + order: -2; + } + + &>.form-controls { + order: -1; + margin-top: 1rem; + align-self: flex-end; + + &.last { + order: -3; + } + } + + &>details { + order: 0; + } + + &>.event { + order: 1; + display: block; + margin: 0.25rem 0; + + // Because the order is reversed + &:last-child { + margin: 0.25rem 0; + } + + &:first-child { + margin: 0; + } + } + + input[type="radio"]:checked~.event { + background: lighten($info, 50) !important; + } + + input[type="radio"]:checked+.event { + background: lighten($info, 45) !important; + } + + @media(prefers-color-scheme: dark) { + input[type="radio"]:checked~.event { + background: #131a3c !important; + } + + input[type="radio"]:checked+.event { + background: #003038 !important; + } + } + } +} + +.markdown-nav { + padding-left: 0; + padding-right: 0; + + .nav-tabs { + padding-left: 0; + margin-bottom: 0; + border-left: 1rem #ddd solid; + } +} + +.blob-nav { + display: inline-block; + padding-left: 0; + padding-right: 0; + + .nav-item:hover { + background: #fff; + } + + @media(prefers-color-scheme: dark) { + .nav-item:hover { + background: inherit; + } + } + + .nav-tabs { + padding-left: 0; + margin-bottom: -3px; + border-bottom: 3px transparent solid; + + .nav-link { + padding: 0 0.5rem; + + &:hover { + color: black; + } + + &.active { + border-bottom: 3px #fff solid; + background: #fff; + } + + @media(prefers-color-scheme: dark) { + color: $gray-400; + + &.active, + &:hover { + border-bottom: 3px $gray-900 solid; + background: $gray-900; + color: $white; + } + } + } + } +} + +.tree-header { + display: flex; + + .breadcrumb { + flex-grow: 1; + width: 100%; + } + + .commit-info { + margin-left: 1rem; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + min-width: 0; + } +} + +dl { + dd { + text-overflow: ellipsis; + overflow-x: hidden; + } +} + +@include media-breakpoint-up(md) { + .blob { + padding-left: 2rem; + + .commit { + float: right; + } + } +} + +.code-viewport { + display: flex; + flex: 1 0 auto; + padding-left: 0; + padding-right: 0; +} diff --git a/templates/base.qtpl b/templates/base.qtpl index db9deee..6ff3d53 100644 --- a/templates/base.qtpl +++ b/templates/base.qtpl @@ -1,11 +1,28 @@ This is a base page template. All the other template pages implement this interface. {% import "context" %} +{% import "crypto/rand" %} +{% import "encoding/hex" %} {% import "strconv" %} {% import "time" %} -{% code +{% code + var Slug = "" + var Session = "" + + func init() { + Session = hex.EncodeToString(generateSmallTimeID()) +} + +func generateSmallTimeID() []byte { + b := make([]byte, 4) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} %} {% interface @@ -54,12 +71,14 @@ Page { Page prints a page implementing Page interface. {% func PageTemplate(p Page, ctx context.Context) %} <!DOCTYPE html> -<html lang="en"> +<html lang="en" data-bs-theme="light"> <head> <meta charset="utf-8"> <link rel="icon" href="data:,"> - <title>{%= p.Title(ctx) %}</title> + <title>{%= p.Title(ctx) %}</title> <link rel="stylesheet" href="/static/main{%s Slug %}.css"> + <link rel="stylesheet" href="/static/theme.{%s Session %}{%s Slug %}.css"> + <html data-bs-theme="dark"> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> @@ -70,5 +89,8 @@ Page prints a page implementing Page interface. </div> </body> {%= p.Script(ctx) %} + <script> + function a(){const e=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.setAttribute("data-bs-theme",e?"dark":"light")}a(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",a); + </script> </html> {% endfunc %} diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go index 796538e..db8afec 100644 --- a/templates/base.qtpl.go +++ b/templates/base.qtpl.go @@ -11,57 +11,77 @@ package templates import "context" //line templates/base.qtpl:4 -import "strconv" +import "crypto/rand" //line templates/base.qtpl:5 -import "time" +import "encoding/hex" + +//line templates/base.qtpl:6 +import "strconv" //line templates/base.qtpl:7 +import "time" + +//line templates/base.qtpl:9 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line templates/base.qtpl:7 +//line templates/base.qtpl:9 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line templates/base.qtpl:8 +//line templates/base.qtpl:11 var Slug = "" +var Session = "" + +func init() { + Session = hex.EncodeToString(generateSmallTimeID()) +} + +func generateSmallTimeID() []byte { + b := make([]byte, 4) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 type Page interface { -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 Title(ctx context.Context) string -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 Content(ctx context.Context) string -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 StreamContent(qw422016 *qt422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 WriteContent(qq422016 qtio422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 Script(ctx context.Context) string -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 StreamScript(qw422016 *qt422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 WriteScript(qq422016 qtio422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 Navbar(ctx context.Context) string -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) -//line templates/base.qtpl:12 +//line templates/base.qtpl:29 } -//line templates/base.qtpl:21 +//line templates/base.qtpl:38 func FromUInttoString(u *uint) string { if u != nil { return strconv.FormatUint(uint64(*u), 10) @@ -69,23 +89,23 @@ func FromUInttoString(u *uint) string { return "" } -//line templates/base.qtpl:31 +//line templates/base.qtpl:48 func TimeFormat(t time.Time) string { return t.Format("02.01.2006") } -//line templates/base.qtpl:36 +//line templates/base.qtpl:53 func Ignore[T any](v T, _ error) T { return v } -//line templates/base.qtpl:42 +//line templates/base.qtpl:59 func IsAuthenticationDisabled(ctx context.Context) bool { t, ok := ctx.Value("disableAuthentication").(bool) return ok && t } -//line templates/base.qtpl:48 +//line templates/base.qtpl:65 func IsLoggedIn(ctx context.Context) bool { t, ok := ctx.Value("logged").(bool) return ok && t @@ -93,74 +113,85 @@ func IsLoggedIn(ctx context.Context) bool { // Page prints a page implementing Page interface. -//line templates/base.qtpl:55 +//line templates/base.qtpl:72 func StreamPageTemplate(qw422016 *qt422016.Writer, p Page, ctx context.Context) { -//line templates/base.qtpl:55 +//line templates/base.qtpl:72 qw422016.N().S(` <!DOCTYPE html> -<html lang="en"> +<html lang="en" data-bs-theme="light"> <head> <meta charset="utf-8"> <link rel="icon" href="data:,"> <title>`) -//line templates/base.qtpl:61 +//line templates/base.qtpl:78 p.StreamTitle(qw422016, ctx) -//line templates/base.qtpl:61 - qw422016.N().S(`</title> +//line templates/base.qtpl:78 + qw422016.N().S(`</title> <link rel="stylesheet" href="/static/main`) -//line templates/base.qtpl:62 +//line templates/base.qtpl:79 + qw422016.E().S(Slug) +//line templates/base.qtpl:79 + qw422016.N().S(`.css"> + <link rel="stylesheet" href="/static/theme.`) +//line templates/base.qtpl:80 + qw422016.E().S(Session) +//line templates/base.qtpl:80 qw422016.E().S(Slug) -//line templates/base.qtpl:62 +//line templates/base.qtpl:80 qw422016.N().S(`.css"> + <html data-bs-theme="dark"> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> `) -//line templates/base.qtpl:67 +//line templates/base.qtpl:86 p.StreamNavbar(qw422016, ctx) -//line templates/base.qtpl:67 +//line templates/base.qtpl:86 qw422016.N().S(` <div class="container"> `) -//line templates/base.qtpl:69 +//line templates/base.qtpl:88 p.StreamContent(qw422016, ctx) -//line templates/base.qtpl:69 +//line templates/base.qtpl:88 qw422016.N().S(` </div> </body> `) -//line templates/base.qtpl:72 +//line templates/base.qtpl:91 p.StreamScript(qw422016, ctx) -//line templates/base.qtpl:72 +//line templates/base.qtpl:91 qw422016.N().S(` + <script> + function a(){const e=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.setAttribute("data-bs-theme",e?"dark":"light")}a(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",a); + </script> </html> `) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 } -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 func WritePageTemplate(qq422016 qtio422016.Writer, p Page, ctx context.Context) { -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 StreamPageTemplate(qw422016, p, ctx) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 qt422016.ReleaseWriter(qw422016) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 } -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 func PageTemplate(p Page, ctx context.Context) string { -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 qb422016 := qt422016.AcquireByteBuffer() -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 WritePageTemplate(qb422016, p, ctx) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 qs422016 := string(qb422016.B) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 qt422016.ReleaseByteBuffer(qb422016) -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 return qs422016 -//line templates/base.qtpl:74 +//line templates/base.qtpl:96 } diff --git a/templates/gititemblob.qtpl b/templates/gititemblob.qtpl index ca3a1fa..c5f412b 100644 --- a/templates/gititemblob.qtpl +++ b/templates/gititemblob.qtpl @@ -13,9 +13,9 @@ type GitItemBlobPage struct { <div class="pathing"> {% stripspace %} {% if len(g.Path) != 0 %} - <a href="{%s url(name, Folder, ref, Root, []string{}) %}">root/</a> + <a href="{%s generateURL(name, Folder, ref, Root, []string{}) %}">root/</a> {% for i, e := range g.Path[:len(g.Path)-1] %} - <a href="{%s url(name, Folder, ref, Root, g.Path[:1+i]) %}">{%s e %}/</a> + <a href="{%s generateURL(name, Folder, ref, Root, g.Path[:1+i]) %}">{%s e %}/</a> {% endfor %} <a>{%s u.LastOrZero(g.Path) %}</a> {% else %} diff --git a/templates/gititemblob.qtpl.go b/templates/gititemblob.qtpl.go index 73742f6..4843882 100644 --- a/templates/gititemblob.qtpl.go +++ b/templates/gititemblob.qtpl.go @@ -70,7 +70,7 @@ func (g *GitItemBlobPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref //line templates/gititemblob.qtpl:15 qw422016.N().S(`<a href="`) //line templates/gititemblob.qtpl:16 - qw422016.E().S(url(name, Folder, ref, Root, []string{})) + qw422016.E().S(generateURL(name, Folder, ref, Root, []string{})) //line templates/gititemblob.qtpl:16 qw422016.N().S(`">root/</a>`) //line templates/gititemblob.qtpl:17 @@ -78,7 +78,7 @@ func (g *GitItemBlobPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref //line templates/gititemblob.qtpl:17 qw422016.N().S(`<a href="`) //line templates/gititemblob.qtpl:18 - qw422016.E().S(url(name, Folder, ref, Root, g.Path[:1+i])) + qw422016.E().S(generateURL(name, Folder, ref, Root, g.Path[:1+i])) //line templates/gititemblob.qtpl:18 qw422016.N().S(`">`) //line templates/gititemblob.qtpl:18 diff --git a/templates/gititemsummary.qtpl b/templates/gititemsummary.qtpl index f39a613..7f56837 100644 --- a/templates/gititemsummary.qtpl +++ b/templates/gititemsummary.qtpl @@ -1,3 +1,4 @@ +{% import "net/url" %} {% import "github.com/go-git/go-git/v5/plumbing" %} {% import "git.gabrielgio.me/cerrado/pkg/git" %} @@ -6,12 +7,26 @@ type GitItemSummaryPage struct { Tags []*git.TagReference Branches []*plumbing.Reference Commits []*git.CommitReference + Hostname string } %} +{% code + +func mergeURL(hostname, name string) string { + s, _ := url.JoinPath(hostname, name) + return s +} + +%} + {% func (g *GitItemSummaryPage) Nav(name, ref string) %}{%= GitItemNav(name, ref, Summary) %}{% endfunc %} {% func (g *GitItemSummaryPage) GitContent(name, ref string) %} +<div class="row event"> + <div class="col-auto">clone: </div> + <div class="col-md">{%s mergeURL(g.Hostname, name) %}</div> +</div> <div class="row"> <div class="col-md-8"> {%= ListTags(name, g.Tags) %} diff --git a/templates/gititemsummary.qtpl.go b/templates/gititemsummary.qtpl.go index d6d20cb..d132cba 100644 --- a/templates/gititemsummary.qtpl.go +++ b/templates/gititemsummary.qtpl.go @@ -5,190 +5,208 @@ package templates //line templates/gititemsummary.qtpl:1 -import "github.com/go-git/go-git/v5/plumbing" +import "net/url" //line templates/gititemsummary.qtpl:2 +import "github.com/go-git/go-git/v5/plumbing" + +//line templates/gititemsummary.qtpl:3 import "git.gabrielgio.me/cerrado/pkg/git" -//line templates/gititemsummary.qtpl:4 +//line templates/gititemsummary.qtpl:5 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line templates/gititemsummary.qtpl:4 +//line templates/gititemsummary.qtpl:5 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line templates/gititemsummary.qtpl:5 +//line templates/gititemsummary.qtpl:6 type GitItemSummaryPage struct { Tags []*git.TagReference Branches []*plumbing.Reference Commits []*git.CommitReference + Hostname string +} + +//line templates/gititemsummary.qtpl:16 +func mergeURL(hostname, name string) string { + s, _ := url.JoinPath(hostname, name) + return s } -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 func (g *GitItemSummaryPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) { -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 StreamGitItemNav(qw422016, name, ref, Summary) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 } -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 func (g *GitItemSummaryPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) { -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 g.StreamNav(qw422016, name, ref) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 qt422016.ReleaseWriter(qw422016) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 } -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 func (g *GitItemSummaryPage) Nav(name, ref string) string { -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 qb422016 := qt422016.AcquireByteBuffer() -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 g.WriteNav(qb422016, name, ref) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 qs422016 := string(qb422016.B) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 qt422016.ReleaseByteBuffer(qb422016) -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 return qs422016 -//line templates/gititemsummary.qtpl:12 +//line templates/gititemsummary.qtpl:23 } -//line templates/gititemsummary.qtpl:14 +//line templates/gititemsummary.qtpl:25 func (g *GitItemSummaryPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref string) { -//line templates/gititemsummary.qtpl:14 +//line templates/gititemsummary.qtpl:25 qw422016.N().S(` +<div class="row event"> + <div class="col-auto">clone: </div> + <div class="col-md">`) +//line templates/gititemsummary.qtpl:28 + qw422016.E().S(mergeURL(g.Hostname, name)) +//line templates/gititemsummary.qtpl:28 + qw422016.N().S(`</div> +</div> <div class="row"> <div class="col-md-8"> `) -//line templates/gititemsummary.qtpl:17 +//line templates/gititemsummary.qtpl:32 StreamListTags(qw422016, name, g.Tags) -//line templates/gititemsummary.qtpl:17 +//line templates/gititemsummary.qtpl:32 qw422016.N().S(` </div> <div class="col-md-4"> <div class="event-list"> `) -//line templates/gititemsummary.qtpl:21 +//line templates/gititemsummary.qtpl:36 for _, b := range g.Branches { -//line templates/gititemsummary.qtpl:21 +//line templates/gititemsummary.qtpl:36 qw422016.N().S(` <div class="row event"> <div class="col-4"> `) -//line templates/gititemsummary.qtpl:24 +//line templates/gititemsummary.qtpl:39 qw422016.E().S(b.Name().Short()) -//line templates/gititemsummary.qtpl:24 +//line templates/gititemsummary.qtpl:39 qw422016.N().S(` </div> <div class="col-8"> <div class="float-end"> <a href="/`) -//line templates/gititemsummary.qtpl:28 +//line templates/gititemsummary.qtpl:43 qw422016.E().S(name) -//line templates/gititemsummary.qtpl:28 +//line templates/gititemsummary.qtpl:43 qw422016.N().S(`/archive/`) -//line templates/gititemsummary.qtpl:28 +//line templates/gititemsummary.qtpl:43 qw422016.E().S(b.Name().Short()) -//line templates/gititemsummary.qtpl:28 +//line templates/gititemsummary.qtpl:43 qw422016.N().S(`.tar.gz">tar.gz</a> <a href="/`) -//line templates/gititemsummary.qtpl:29 +//line templates/gititemsummary.qtpl:44 qw422016.E().S(name) -//line templates/gititemsummary.qtpl:29 +//line templates/gititemsummary.qtpl:44 qw422016.N().S(`/tree/`) -//line templates/gititemsummary.qtpl:29 +//line templates/gititemsummary.qtpl:44 qw422016.E().S(b.Name().Short()) -//line templates/gititemsummary.qtpl:29 +//line templates/gititemsummary.qtpl:44 qw422016.N().S(`/">tree</a> <a href="/`) -//line templates/gititemsummary.qtpl:30 +//line templates/gititemsummary.qtpl:45 qw422016.E().S(name) -//line templates/gititemsummary.qtpl:30 +//line templates/gititemsummary.qtpl:45 qw422016.N().S(`/log/`) -//line templates/gititemsummary.qtpl:30 +//line templates/gititemsummary.qtpl:45 qw422016.E().S(b.Name().Short()) -//line templates/gititemsummary.qtpl:30 +//line templates/gititemsummary.qtpl:45 qw422016.N().S(`/">log</a> </div> </div> </div> `) -//line templates/gititemsummary.qtpl:34 +//line templates/gititemsummary.qtpl:49 } -//line templates/gititemsummary.qtpl:34 +//line templates/gititemsummary.qtpl:49 qw422016.N().S(` </div> </div> <a class="more" href="/`) -//line templates/gititemsummary.qtpl:37 +//line templates/gititemsummary.qtpl:52 qw422016.E().S(name) -//line templates/gititemsummary.qtpl:37 +//line templates/gititemsummary.qtpl:52 qw422016.N().S(`/refs/">[ see refs... ]</a> </div> <div class="row"> <div class="event-list"> `) -//line templates/gititemsummary.qtpl:41 +//line templates/gititemsummary.qtpl:56 for _, c := range g.Commits { -//line templates/gititemsummary.qtpl:41 +//line templates/gititemsummary.qtpl:56 qw422016.N().S(` `) -//line templates/gititemsummary.qtpl:42 +//line templates/gititemsummary.qtpl:57 StreamCommit(qw422016, name, c, false) -//line templates/gititemsummary.qtpl:42 +//line templates/gititemsummary.qtpl:57 qw422016.N().S(` `) -//line templates/gititemsummary.qtpl:43 +//line templates/gititemsummary.qtpl:58 } -//line templates/gititemsummary.qtpl:43 +//line templates/gititemsummary.qtpl:58 qw422016.N().S(` </div> <a class="more" href="/`) -//line templates/gititemsummary.qtpl:45 +//line templates/gititemsummary.qtpl:60 qw422016.E().S(name) -//line templates/gititemsummary.qtpl:45 +//line templates/gititemsummary.qtpl:60 qw422016.N().S(`/log/`) -//line templates/gititemsummary.qtpl:45 +//line templates/gititemsummary.qtpl:60 qw422016.E().S(ref) -//line templates/gititemsummary.qtpl:45 +//line templates/gititemsummary.qtpl:60 qw422016.N().S(`/">[ see log... ]</a> </div> `) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 } -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 func (g *GitItemSummaryPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) { -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 g.StreamGitContent(qw422016, name, ref) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 qt422016.ReleaseWriter(qw422016) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 } -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 func (g *GitItemSummaryPage) GitContent(name, ref string) string { -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 qb422016 := qt422016.AcquireByteBuffer() -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 g.WriteGitContent(qb422016, name, ref) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 qs422016 := string(qb422016.B) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 qt422016.ReleaseByteBuffer(qb422016) -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 return qs422016 -//line templates/gititemsummary.qtpl:47 +//line templates/gititemsummary.qtpl:62 } diff --git a/templates/gititemtree.qtpl b/templates/gititemtree.qtpl index 2753e24..677d8ec 100644 --- a/templates/gititemtree.qtpl +++ b/templates/gititemtree.qtpl @@ -15,7 +15,7 @@ ) %} -{% code func url(name, mode, ref, filename string, path []string) string { +{% code func generateURL(name, mode, ref, filename string, path []string) string { return u.NewPathing(). AddPath(name). AddPath(mode). @@ -32,9 +32,9 @@ <div class="pathing"> {% stripspace %} {% if len(g.Path) != 0 %} - <a href="{%s url(name, Folder, ref, Root, []string{}) %}">root/</a> + <a href="{%s generateURL(name, Folder, ref, Root, []string{}) %}">root/</a> {% for i, e := range g.Path[:len(g.Path)-1] %} - <a href="{%s url(name, Folder, ref, Root, g.Path[:1+i]) %}">{%s e %}/</a> + <a href="{%s generateURL(name, Folder, ref, Root, g.Path[:1+i]) %}">{%s e %}/</a> {% endfor %} <a>{%s u.LastOrZero(g.Path) %}</a> {% else %} @@ -46,7 +46,7 @@ <div class="col-md-12"> <div class="tree-list"> {% if len(g.Path) != 0 %} - <div class="mode"><a href="{%s url(name, Folder, ref, g.Path[len(g.Path)-1], g.Path[:len(g.Path)-1]) %}">..</a></div> + <div class="mode"><a href="{%s generateURL(name, Folder, ref, g.Path[len(g.Path)-1], g.Path[:len(g.Path)-1]) %}">..</a></div> <div class="name tree"></div> <div class="commit"></div> <div class="date"></div> @@ -55,13 +55,13 @@ {% for _, e := range g.Tree.Entries %} {% if e.Mode.IsFile() %} <div class="mode">{%s Ignore(e.Mode.ToOSFileMode()).String() %}</div> - <div class="name blob"><a href="{%s url(name, Blob, ref, e.Name, g.Path) %}">{%s e.Name %}</a></div> + <div class="name blob"><a href="{%s generateURL(name, Blob, ref, e.Name, g.Path) %}">{%s e.Name %}</a></div> {% elseif e.Mode == filemode.Submodule %} <div class="mode">m---------</div> <div class="name tree">{%s e.Name %} (submodule)</div> {% else %} <div class="mode">d---------</div> - <div class="name tree"><a href="{%s url(name, Folder, ref, e.Name, g.Path) %}">{%s e.Name %}</a></div> + <div class="name tree"><a href="{%s generateURL(name, Folder, ref, e.Name, g.Path) %}">{%s e.Name %}</a></div> {% endif %} <div class="commit"></div> <div class="date"></div> diff --git a/templates/gititemtree.qtpl.go b/templates/gititemtree.qtpl.go index 9116cd7..7a378cc 100644 --- a/templates/gititemtree.qtpl.go +++ b/templates/gititemtree.qtpl.go @@ -40,7 +40,7 @@ const ( ) //line templates/gititemtree.qtpl:18 -func url(name, mode, ref, filename string, path []string) string { +func generateURL(name, mode, ref, filename string, path []string) string { return u.NewPathing(). AddPath(name). AddPath(mode). @@ -94,7 +94,7 @@ func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer, name, ref //line templates/gititemtree.qtpl:34 qw422016.N().S(`<a href="`) //line templates/gititemtree.qtpl:35 - qw422016.E().S(url(name, Folder, ref, Root, []string{})) + qw422016.E().S(generateURL(name, Folder, ref, Root, []string{})) //line templates/gititemtree.qtpl:35 qw422016.N().S(`">root/</a>`) //line templates/gititemtree.qtpl:36 @@ -102,7 +102,7 @@ func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer, name, ref //line templates/gititemtree.qtpl:36 qw422016.N().S(`<a href="`) //line templates/gititemtree.qtpl:37 - qw422016.E().S(url(name, Folder, ref, Root, g.Path[:1+i])) + qw422016.E().S(generateURL(name, Folder, ref, Root, g.Path[:1+i])) //line templates/gititemtree.qtpl:37 qw422016.N().S(`">`) //line templates/gititemtree.qtpl:37 @@ -136,7 +136,7 @@ func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer, name, ref qw422016.N().S(` <div class="mode"><a href="`) //line templates/gititemtree.qtpl:49 - qw422016.E().S(url(name, Folder, ref, g.Path[len(g.Path)-1], g.Path[:len(g.Path)-1])) + qw422016.E().S(generateURL(name, Folder, ref, g.Path[len(g.Path)-1], g.Path[:len(g.Path)-1])) //line templates/gititemtree.qtpl:49 qw422016.N().S(`">..</a></div> <div class="name tree"></div> @@ -165,7 +165,7 @@ func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer, name, ref qw422016.N().S(`</div> <div class="name blob"><a href="`) //line templates/gititemtree.qtpl:58 - qw422016.E().S(url(name, Blob, ref, e.Name, g.Path)) + qw422016.E().S(generateURL(name, Blob, ref, e.Name, g.Path)) //line templates/gititemtree.qtpl:58 qw422016.N().S(`">`) //line templates/gititemtree.qtpl:58 @@ -191,7 +191,7 @@ func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer, name, ref <div class="mode">d---------</div> <div class="name tree"><a href="`) //line templates/gititemtree.qtpl:64 - qw422016.E().S(url(name, Folder, ref, e.Name, g.Path)) + qw422016.E().S(generateURL(name, Folder, ref, e.Name, g.Path)) //line templates/gititemtree.qtpl:64 qw422016.N().S(`">`) //line templates/gititemtree.qtpl:64 |
