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/web/auth.go +13−9

@@ -199,6 +199,10 @@ func (w *Web) recordPageView(r *http.Request, sw *statusWriter) {
199 199 if !strings.HasPrefix(sw.Header().Get("Content-Type"), "text/html") {
200 200 return
201 201 }
202 // A module discovery probe is answered with HTML but is not a page.
203 if r.URL.Query().Get("go-get") != "" {
204 return
205 }
202 206 account := ""
203 207 if pr := w.principal(r); pr != nil {
204 208 account = pr.account.Name
@@ -212,7 +216,7 @@ func (w *Web) recordPageView(r *http.Request, sw *statusWriter) {
212 216 // pages are read by the same few people watching the instance, so counting them
213 217 // would measure administration rather than readership.
214 218 func adminPath(path string) bool {
215 return path == "/admin" || path == "/status" || path == "/users" || strings.HasPrefix(path, "/users/")
219 return path == "/-/admin" || path == "/-/status" || path == "/-/users" || strings.HasPrefix(path, "/-/users/")
216 220 }
217 221
218 222 // sessionFromCookie returns the first valid session cookie and the name it came
@@ -444,25 +448,25 @@ func (w *Web) page(r *http.Request, title string) Page {
444 448 v = v[:12]
445 449 }
446 450 if len(snap.CSS) > 0 {
447 p.InstanceCSS = "/static/custom/profile/custom.css?v=" + v
451 p.InstanceCSS = "/-/static/custom/profile/custom.css?v=" + v
448 452 }
449 453 if len(snap.Favicon) > 0 {
450 p.InstanceFavicon = "/static/custom/profile/favicon.svg?v=" + v
454 p.InstanceFavicon = "/-/static/custom/profile/favicon.svg?v=" + v
451 455 }
452 456 for _, t := range snap.Themes {
453 457 p.InstanceThemes = append(p.InstanceThemes, instanceThemeView{
454 458 ID: t.ID,
455 459 Name: t.Name,
456 URL: "/static/custom/themes/" + t.ID + ".css?v=" + v,
460 URL: "/-/static/custom/themes/" + t.ID + ".css?v=" + v,
457 461 })
458 462 }
459 463 }
460 464 }
461 465 if w.Repos != nil {
462 // `{name}` is a repository only under /r/. A profile (/u/{name}) and the
463 // admin account page (/users/{name}) use the same path variable for an
464 // account, and neither should cost a failed repository lookup.
465 if name := r.PathValue("name"); name != "" && strings.HasPrefix(r.URL.Path, "/r/") {
466 // A path outside /-/ names a repository in its first segment. A profile
467 // (/-/u/{name}) and the admin account page (/-/users/{name}) share the
468 // `{name}` wildcard but are the forge's own pages, not repositories.
469 if name := r.PathValue("name"); name != "" && !strings.HasPrefix(r.URL.Path, "/-/") {
466 470 p.RepoName = name
467 471 if m, err := w.repo(p.memo, name); err == nil {
468 472 p.CloneURL = w.cloneURL(r, name)
@@ -594,7 +598,7 @@ func (w *Web) requireHTMLUser(rw http.ResponseWriter, r *http.Request) (*princip
594 598 pr := w.principal(r)
595 599 if pr == nil {
596 600 next := url.QueryEscape(r.URL.RequestURI())
597 http.Redirect(rw, r, "/login?next="+next, http.StatusSeeOther)
601 http.Redirect(rw, r, "/-/login?next="+next, http.StatusSeeOther)
598 602 return nil, false
599 603 }
600 604 return pr, true