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/status_test.go +8−8

@@ -23,7 +23,7 @@ func (f *fixture) adminGet(t *testing.T, path string) (int, string) {
23 23 func TestAdminOverviewSummarizesTheInstance(t *testing.T) {
24 24 t.Parallel()
25 25 f := newFixture(t)
26 code, body := f.adminGet(t, "/admin")
26 code, body := f.adminGet(t, "/-/admin")
27 27 if code != 200 {
28 28 t.Fatalf("status page = %d:\n%s", code, body)
29 29 }
@@ -56,12 +56,12 @@ func TestAdminConfigurationShowsToAdmins(t *testing.T) {
56 56
57 57 // The page is instance-admin only: anonymous goes to login, and a signed-in
58 58 // non-admin gets the same 404 as a missing page.
59 if resp, _ := f.browserGet(t, "/admin", ""); resp.StatusCode != http.StatusSeeOther || !strings.Contains(resp.Header.Get("Location"), "/login") {
59 if resp, _ := f.browserGet(t, "/-/admin", ""); resp.StatusCode != http.StatusSeeOther || !strings.Contains(resp.Header.Get("Location"), "/-/login") {
60 60 t.Fatalf("anonymous status page = %d %q", resp.StatusCode, resp.Header.Get("Location"))
61 61 }
62 62 f.addWriter(t)
63 63 _, writerCookie := f.browserSession(t, f.writerID)
64 if resp, _ := f.browserGet(t, "/admin?tab=configuration", writerCookie); resp.StatusCode != http.StatusNotFound {
64 if resp, _ := f.browserGet(t, "/-/admin?tab=configuration", writerCookie); resp.StatusCode != http.StatusNotFound {
65 65 t.Fatalf("non-admin status page = %d, want 404", resp.StatusCode)
66 66 }
67 67
@@ -69,7 +69,7 @@ func TestAdminConfigurationShowsToAdmins(t *testing.T) {
69 69 t.Fatal(err)
70 70 }
71 71 _, cookie := f.browserSession(t, f.ownerID)
72 resp, body := f.browserGet(t, "/admin?tab=configuration", cookie)
72 resp, body := f.browserGet(t, "/-/admin?tab=configuration", cookie)
73 73 if resp.StatusCode != 200 {
74 74 t.Fatalf("admin status page = %d:\n%s", resp.StatusCode, body)
75 75 }
@@ -107,7 +107,7 @@ func TestAdminPagesAreNotCountedAsPageViews(t *testing.T) {
107 107
108 108 // The status page must not count itself, or a monitor turns the metric into
109 109 // a measure of polling.
110 if code, _ := f.adminGet(t, "/admin"); code != 200 {
110 if code, _ := f.adminGet(t, "/-/admin"); code != 200 {
111 111 t.Fatalf("status = %d", code)
112 112 }
113 113 stats, err = f.srv.Web.PageViews.Stats(time.Now().UTC())
@@ -129,7 +129,7 @@ func TestAdminTabsSplitTheSections(t *testing.T) {
129 129 t.Fatal(err)
130 130 }
131 131
132 code, storage := f.adminGet(t, "/admin?tab=storage")
132 code, storage := f.adminGet(t, "/-/admin?tab=storage")
133 133 if code != 200 || !strings.Contains(storage, "Repositories") || !strings.Contains(storage, "LFS and build objects") {
134 134 t.Fatalf("storage tab = %d:\n%s", code, storage)
135 135 }
@@ -139,7 +139,7 @@ func TestAdminTabsSplitTheSections(t *testing.T) {
139 139 }
140 140 }
141 141
142 code, runners := f.adminGet(t, "/admin?tab=runners")
142 code, runners := f.adminGet(t, "/-/admin?tab=runners")
143 143 if code != 200 || !strings.Contains(runners, "Runners and queue") || !strings.Contains(runners, "Queue") {
144 144 t.Fatalf("runners tab = %d:\n%s", code, runners)
145 145 }
@@ -147,7 +147,7 @@ func TestAdminTabsSplitTheSections(t *testing.T) {
147 147 t.Fatalf("runners tab shows overview content:\n%s", runners)
148 148 }
149 149
150 code, config := f.adminGet(t, "/admin?tab=configuration")
150 code, config := f.adminGet(t, "/-/admin?tab=configuration")
151 151 if code != 200 || !strings.Contains(config, "Configuration") || !strings.Contains(config, "Instance and access") {
152 152 t.Fatalf("configuration tab = %d:\n%s", code, config)
153 153 }