aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--config.example.scfg6
-rw-r--r--go.mod43
-rw-r--r--go.sum140
-rw-r--r--pkg/config/config.go60
-rw-r--r--pkg/config/config_test.go38
-rw-r--r--pkg/ext/auth.go22
-rw-r--r--pkg/ext/compression.go31
-rw-r--r--pkg/ext/log.go4
-rw-r--r--pkg/ext/request.go14
-rw-r--r--pkg/ext/router.go24
-rw-r--r--pkg/git/git.go101
-rw-r--r--pkg/handler/about/handler.go3
-rw-r--r--pkg/handler/auth/login.go4
-rw-r--r--pkg/handler/git/handler.go69
-rw-r--r--pkg/handler/router.go18
-rw-r--r--pkg/handler/static/handler.go63
-rw-r--r--pkg/service/git.go29
m---------scss/bootstrap0
-rw-r--r--scss/main.scss22
-rw-r--r--scss/tree.scss399
-rw-r--r--templates/base.qtpl28
-rw-r--r--templates/base.qtpl.go131
-rw-r--r--templates/gititemblob.qtpl4
-rw-r--r--templates/gititemblob.qtpl.go4
-rw-r--r--templates/gititemsummary.qtpl15
-rw-r--r--templates/gititemsummary.qtpl.go150
-rw-r--r--templates/gititemtree.qtpl12
-rw-r--r--templates/gititemtree.qtpl.go12
29 files changed, 1152 insertions, 302 deletions
diff --git a/README.md b/README.md
index 0954b92..adb4809 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/go.mod b/go.mod
index 3ba555f..eb6a5c6 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index 59c8d96..a8d56e7 100644
--- a/go.sum
+++ b/go.sum
@@ -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:&emsp;</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:&emsp;</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