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/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 }