Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Serve repositories from the root of the URL space

ed03b3d1fa16ef6b55e2773aaf10e3be13282c3a · Keenan Nemetz · 4h ago · parent d37d301f

Serve repositories from the root of the URL space

A repository is now /<name> and its Git transport /<name>.git, so the URL a
person opens, the URL a clone uses, and the Go module path are the same name.
The forge's own pages moved under /-/ (admin, accounts, the directory, the
feed, sign-in, assets), a prefix no repository name can start with: names are
[a-z0-9...] and cannot begin with "-". That leaves the router with two disjoint
spaces and removes the /r/ and /git/ prefixes, which existed only to keep
repositories away from the forge's own paths.

Routes: repoRoutes lose /r/; global, auth and admin routes gain /-/; assets
move to /-/static/ because a subtree pattern like /static/ cannot coexist with
one-segment-wildcard repo routes — Go's mux reports them as conflicting
neither-more-specific patterns. The catch-all still renders the styled 404.

Transport: githttp.Route parses /<name>.git/<rest> instead of a /git/ prefix,
and forgepaths.go hands such requests to the proxy before the mux, because a
wildcard fills a whole segment and cannot match "<name>.git". LFS stays on the
mux (<name>.git/info/lfs/...), and the Go module discovery probe moves to the
same pre-router so a package path under a public repository is answered even
when its first segments name a page tab.

Names: repo.ValidateName now refuses the four root paths the forge must keep —
static, robots.txt, sitemap.xml, sitemap-pages.xml. Everything else is an
ordinary repository name, including admin, u, and activity.

Docs and the packaged URLs follow the new layout; robots.txt, the sitemaps and
the instance activity feed point at the new paths. No aliases: the old /r/ and
/git/ URLs are gone.

Verified with ./vole workflow check and ./vole workflow run verify.

server/forgepaths.go +54

@@ -0,0 +1,54 @@
1 package server
2
3 // The two paths the forge serves that are not pages: a repository's Git
4 // transport at <name>.git/…, and the go command's module discovery probe on a
5 // module path. Both decide from the first path segment alone, so they run before
6 // the mux instead of as routes; a wildcard in a route fills a whole segment, and
7 // neither "<name>.git" nor a module path with a package suffix can be written as
8 // one.
9
10 import (
11 "net/http"
12 "path"
13 "strings"
14
15 "rodent.software/vole/server/githttp"
16 )
17
18 // forgeHandler is the mux with the non-page paths in front of it. It sits inside
19 // the web middleware, so the session, the security headers, the compressor and
20 // the page-view recorder behave exactly as they do for a page.
21 func (s *Server) forgeHandler(next http.Handler) http.Handler {
22 return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
23 if s.gitTransport(rw, r) {
24 return
25 }
26 if s.Web.GoModuleMeta(rw, r) {
27 return
28 }
29 next.ServeHTTP(rw, r)
30 })
31 }
32
33 // gitTransport serves a clone or fetch from <name>.git/…, the URL the clone
34 // menu and the go-import tag advertise. githttp.Route parses that shape; this
35 // only decides whether the request belongs to the transport or to the mux.
36 //
37 // LFS requests are left to the mux: a client asks <name>.git/info/lfs/…, and
38 // those handlers own the batch and object endpoints.
39 func (s *Server) gitTransport(rw http.ResponseWriter, r *http.Request) bool {
40 if s.Proxy == nil || s.Cfg == nil || !s.Cfg.GitHTTP {
41 return false
42 }
43 // An unclean path is the mux's business: it redirects to the cleaned one, and
44 // the transport must not be the one place that skips that check.
45 if path.Clean(r.URL.Path) != r.URL.Path {
46 return false
47 }
48 _, rest, ok := githttp.Route(r.URL.Path)
49 if !ok || rest == "" || strings.HasPrefix(rest, "info/lfs") {
50 return false
51 }
52 s.Proxy.ServeHTTP(rw, r)
53 return true
54 }