vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Phase 7 slice 1: review on the web, so approving does not start with a clone
78912785b00227cffe3f8fb26b057cd60159a6b4 · Keenan Nemetz · 18d ago · parent 9bea7f11
Phase 7 slice 1: review on the web, so approving does not start with a clone Phase 7 is re-scoped: signing is deferred (the SSH handshake already establishes who pushed, and there is no third-party verifier here), and the phase is now about the experience of using the forge. - GET /r/<repo>/commit/<sha>: a commit's metadata and the patch it introduced, per-file hunks with line numbers; the commit list links to it - change requests show the same diff, computed three-dot: a review is what the branch adds since it diverged, not every commit main gained meanwhile - diffs are capped at 512 KiB and labelled when cut, binary files are named rather than faked, and every line is escaped (a <script> line is a test) - the repository README renders on its home page and markdown blobs render as markdown, through the sanitiser issues already use
internal/web/web.go +30−1
| @@ -140,6 +140,17 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 140 | 140 | if w.Runs != nil { | |
| 141 | 141 | recentRuns, _ = w.Runs.Recent(m.Name, 8) | |
| 142 | 142 | } | |
| 143 | // A repository's README is the front page of most projects; showing the file | ||
| 144 | // list without it makes the repo page look like a directory listing. | ||
| 145 | var readme template.HTML | ||
| 146 | if commit != "" { | ||
| 147 | for _, name := range []string{"README.md", "readme.md", "Readme.md", "README.markdown"} { | ||
| 148 | if data, err := g.BlobAt(commit, name); err == nil { | ||
| 149 | readme = w.renderMarkdown(m.Name, string(data)) | ||
| 150 | break | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 143 | 154 | // A site is a branch, so the page can link to it whether or not the branch | |
| 144 | 155 | // exists yet: pushing it makes the link true, and until then it 404s. | |
| 145 | 156 | site := "" | |
| @@ -165,6 +176,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 165 | 176 | Branches []string | |
| 166 | 177 | IssueCount int | |
| 167 | 178 | Site string | |
| 179 | Readme template.HTML | ||
| 168 | 180 | Runs []*runs.Run | |
| 169 | 181 | }{ | |
| 170 | 182 | Page: w.base(m.Name), | |
| @@ -180,6 +192,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 180 | 192 | Branches: names, | |
| 181 | 193 | IssueCount: issueCount, | |
| 182 | 194 | Site: site, | |
| 195 | Readme: readme, | ||
| 183 | 196 | Runs: recentRuns, | |
| 184 | 197 | }, 0) | |
| 185 | 198 | } | |
| @@ -258,17 +271,24 @@ func (w *Web) blob(rw http.ResponseWriter, r *http.Request, raw bool) { | |||
| 258 | 271 | content = string(data[:maxInlineBytes]) | |
| 259 | 272 | truncated = true | |
| 260 | 273 | } | |
| 274 | // Markdown is the one thing a reader expects to see rendered; everything else | ||
| 275 | // is shown as bytes in a <pre>, which is what a source file is. | ||
| 276 | var rendered template.HTML | ||
| 277 | if !binary && !truncated && isMarkdown(p) { | ||
| 278 | rendered = w.renderMarkdown(m.Name, content) | ||
| 279 | } | ||
| 261 | 280 | w.render(rw, "blob", struct { | |
| 262 | 281 | Page | |
| 263 | 282 | Name string | |
| 264 | 283 | Ref string | |
| 265 | 284 | Path string | |
| 266 | 285 | Content string | |
| 286 | Markdown template.HTML | ||
| 267 | 287 | Binary bool | |
| 268 | 288 | Truncated bool | |
| 269 | 289 | Size int64 | |
| 270 | 290 | Crumbs []crumb | |
| 271 | }{w.base(m.Name + " · " + p), m.Name, ref, p, content, binary, truncated, int64(len(data)), crumbs(p)}, 0) | ||
| 291 | }{w.base(m.Name + " · " + p), m.Name, ref, p, content, rendered, binary, truncated, int64(len(data)), crumbs(p)}, 0) | ||
| 272 | 292 | } | |
| 273 | 293 | ||
| 274 | 294 | func (w *Web) Commits(rw http.ResponseWriter, r *http.Request) { | |
| @@ -381,3 +401,12 @@ func humanSince(t int64) string { | |||
| 381 | 401 | return fmt.Sprintf("%dd ago", int(d.Hours()/24)) | |
| 382 | 402 | } | |
| 383 | 403 | } | |
| 404 | |||
| 405 | // isMarkdown names the extensions the web renders instead of showing as source. | ||
| 406 | func isMarkdown(path string) bool { | ||
| 407 | switch strings.ToLower(path[strings.LastIndexByte(path, '.')+1:]) { | ||
| 408 | case "md", "markdown", "mdown", "mkd": | ||
| 409 | return true | ||
| 410 | } | ||
| 411 | return false | ||
| 412 | } | ||