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/activity.go +10−10

@@ -120,7 +120,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
120 120 events = append(events, activityEvent{
121 121 Kind: "issue", Icon: "circle-dot", Time: iss.Created,
122 122 Actor: w.who(iss.Author), ActorName: iss.Author, Title: iss.Title,
123 Detail: "opened issue", URL: "/r/" + m.Name + "/issues/" + iss.ID,
123 Detail: "opened issue", URL: "/" + m.Name + "/issues/" + iss.ID,
124 124 Assignees: iss.Assignees,
125 125 })
126 126 for _, c := range iss.Comments {
@@ -128,7 +128,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
128 128 Kind: "issue", Icon: "message-square", Time: c.Created,
129 129 Actor: w.who(c.Author), ActorName: c.Author, Title: iss.Title,
130 130 Detail: "commented on issue",
131 URL: "/r/" + m.Name + "/issues/" + iss.ID + "#c-" + c.ID,
131 URL: "/" + m.Name + "/issues/" + iss.ID + "#c-" + c.ID,
132 132 Assignees: iss.Assignees,
133 133 })
134 134 }
@@ -146,14 +146,14 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
146 146 events = append(events, activityEvent{
147 147 Kind: "change", Icon: "git-pull-request", Time: req.Created,
148 148 Actor: w.who(req.Author), ActorName: req.Author, Title: req.Title,
149 Detail: "opened change", URL: "/r/" + m.Name + "/changes/" + req.ID,
149 Detail: "opened change", URL: "/" + m.Name + "/changes/" + req.ID,
150 150 })
151 151 for _, c := range req.Comments {
152 152 events = append(events, activityEvent{
153 153 Kind: "change", Icon: "message-square", Time: c.Created,
154 154 Actor: w.who(c.Author), ActorName: c.Author, Title: req.Title,
155 155 Detail: "commented on change",
156 URL: "/r/" + m.Name + "/changes/" + req.ID + "#c-" + c.ID,
156 URL: "/" + m.Name + "/changes/" + req.ID + "#c-" + c.ID,
157 157 })
158 158 }
159 159 for _, a := range req.Approvals {
@@ -161,7 +161,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
161 161 Kind: "change", Icon: "check", Time: a.Created,
162 162 Actor: w.who(a.Principal), ActorName: a.Principal, Title: req.Title,
163 163 Detail: "approved change",
164 URL: "/r/" + m.Name + "/changes/" + req.ID,
164 URL: "/" + m.Name + "/changes/" + req.ID,
165 165 })
166 166 }
167 167 }
@@ -182,7 +182,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
182 182 events = append(events, activityEvent{
183 183 Kind: "issue", Icon: "circle-dot", Time: sc.Time,
184 184 Actor: w.who(sc.Actor), ActorName: sc.Actor, Title: iss.Title,
185 Detail: issueStateDetail(sc.State), URL: "/r/" + m.Name + "/issues/" + sc.ID,
185 Detail: issueStateDetail(sc.State), URL: "/" + m.Name + "/issues/" + sc.ID,
186 186 Assignees: iss.Assignees,
187 187 })
188 188 case "request":
@@ -196,7 +196,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
196 196 events = append(events, activityEvent{
197 197 Kind: "change", Icon: "git-pull-request", Time: sc.Time,
198 198 Actor: w.who(sc.Actor), ActorName: sc.Actor, Title: req.Title,
199 Detail: requestStateDetail(sc.State), URL: "/r/" + m.Name + "/changes/" + sc.ID,
199 Detail: requestStateDetail(sc.State), URL: "/" + m.Name + "/changes/" + sc.ID,
200 200 })
201 201 }
202 202 }
@@ -210,7 +210,7 @@ func (w *Web) repoActivity(memo *requestMemo, m *repo.Meta, kind, actor, assigne
210 210 events = append(events, activityEvent{
211 211 Kind: "release", Icon: "tag", Time: time.Unix(t.When, 0).UTC(),
212 212 Actor: t.Tagger, Title: t.Name, Detail: "released",
213 URL: "/r/" + m.Name + "/releases/" + t.Name,
213 URL: "/" + m.Name + "/releases/" + t.Name,
214 214 })
215 215 }
216 216 }
@@ -392,9 +392,9 @@ func repoActivityURL(name, kind, actor, assignee string) func(int) string {
392 392 v.Set("page", strconv.Itoa(n))
393 393 }
394 394 if q := v.Encode(); q != "" {
395 return "/r/" + name + "/activity?" + q
395 return "/" + name + "/activity?" + q
396 396 }
397 return "/r/" + name + "/activity"
397 return "/" + name + "/activity"
398 398 }
399 399 }
400 400