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.

Code

396 commit(s) on main

Turn off the disk reserve for test-spawned runners Keenan Nemetz · 14m ago c284cb2c
history blame raw 2.6 KiB
1package web
2
3// File history and blame are the two answers a blob page cannot give: how this
4// path changed over time, and which commit last touched each line.
5
6import (
7 "net/http"
8
9 "rodent.software/vole/core/git"
10)
11
12func (w *Web) FileHistory(rw http.ResponseWriter, r *http.Request) {
13 m, ok := w.open(rw, r)
14 if !ok {
15 return
16 }
17 ref, path := r.PathValue("ref"), r.PathValue("path")
18 if !safePath(path) {
19 w.notFound(rw, r)
20 return
21 }
22 release, ok := w.gitSlot(rw, r)
23 if !ok {
24 return
25 }
26 defer release()
27 g, cancel := gitRepo(r, m)
28 defer cancel()
29 commit, _ := g.RevParse(ref)
30 if commit == "" {
31 w.notFound(rw, r)
32 return
33 }
34 kind, err := g.ObjectType(commit + ":" + path)
35 if err != nil {
36 w.notFound(rw, r)
37 return
38 }
39 commits, err := g.LogPath(commit, path, 100, kind == "blob")
40 if err != nil {
41 w.serverError(rw, r, "cannot read file history: "+err.Error())
42 return
43 }
44 w.render(rw, "file_history", struct {
45 Page
46 Name string
47 Ref string
48 Path string
49 Commit string
50 Commits []git.Commit
51 IsBlob bool
52 }{
53 Page: w.page(r, m.Name+" · history · "+path), Name: m.Name, Ref: ref,
54 Path: path, Commit: commit, Commits: commits, IsBlob: kind == "blob",
55 }, 0)
56}
57
58type blameBlock struct {
59 SHA string
60 Author string
61 When int64
62 Lines []git.BlameLine
63}
64
65func (w *Web) Blame(rw http.ResponseWriter, r *http.Request) {
66 m, ok := w.open(rw, r)
67 if !ok {
68 return
69 }
70 ref, path := r.PathValue("ref"), r.PathValue("path")
71 if !safePath(path) {
72 w.notFound(rw, r)
73 return
74 }
75 release, ok := w.gitSlot(rw, r)
76 if !ok {
77 return
78 }
79 defer release()
80 g, cancel := gitRepo(r, m)
81 defer cancel()
82 commit, _ := g.RevParse(ref)
83 if commit == "" {
84 w.notFound(rw, r)
85 return
86 }
87 if kind, err := g.ObjectType(commit + ":" + path); err != nil || kind != "blob" {
88 w.notFound(rw, r)
89 return
90 }
91 lines, truncated, err := g.Blame(commit, path, 1<<20)
92 if err != nil {
93 w.serverError(rw, r, "cannot blame that file: "+err.Error())
94 return
95 }
96 var blocks []blameBlock
97 for _, line := range lines {
98 if len(blocks) == 0 || blocks[len(blocks)-1].SHA != line.SHA {
99 blocks = append(blocks, blameBlock{SHA: line.SHA, Author: line.Author, When: line.When})
100 }
101 last := &blocks[len(blocks)-1]
102 last.Lines = append(last.Lines, line)
103 }
104 w.render(rw, "blame", struct {
105 Page
106 Name string
107 Ref string
108 Path string
109 Commit string
110 Blocks []blameBlock
111 Lines int
112 Truncated bool
113 }{
114 Page: w.page(r, m.Name+" · blame · "+path), Name: m.Name, Ref: ref,
115 Path: path, Commit: commit, Blocks: blocks, Lines: len(lines), Truncated: truncated,
116 }, 0)
117}
118