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.

Phase 7 slice 1: review on the web, so approving does not start with a clone

78912785b00227cffe3f8fb26b057cd60159a6b4 · Keenan Nemetz · 17d 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/diff_test.go +152

@@ -0,0 +1,152 @@
1 package web
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 // The parser reads what git prints. These fixtures are real `git diff` output,
9 // because a hand-written approximation is what lets a parser drift from git.
10 const patchTwoFiles = `diff --git a/readme.md b/readme.md
11 index 1111111..2222222 100644
12 --- a/readme.md
13 +++ b/readme.md
14 @@ -1,3 +1,4 @@
15 # Title
16
17 -old line
18 +new line
19 +another line
20 diff --git a/notes.txt b/notes.txt
21 deleted file mode 100644
22 index 3333333..0000000
23 --- a/notes.txt
24 +++ /dev/null
25 @@ -1,2 +0,0 @@
26 -gone
27 -gone too
28 `
29
30 func TestParseDiff(t *testing.T) {
31 files := parseDiff(patchTwoFiles)
32 if len(files) != 2 {
33 t.Fatalf("parsed %d files, want 2: %+v", len(files), files)
34 }
35 first := files[0]
36 if first.OldPath != "readme.md" || first.NewPath != "readme.md" {
37 t.Fatalf("paths = %q -> %q", first.OldPath, first.NewPath)
38 }
39 if first.Added != 2 || first.Removed != 1 {
40 t.Fatalf("counts = +%d −%d, want +2 −1", first.Added, first.Removed)
41 }
42 if len(first.Hunks) != 1 || len(first.Hunks[0].Lines) != 5 {
43 t.Fatalf("hunk shape = %+v", first.Hunks)
44 }
45 lines := first.Hunks[0].Lines
46 if lines[0].Kind != "ctx" || lines[0].Text != "# Title" || lines[0].OldNo != 1 || lines[0].NewNo != 1 {
47 t.Fatalf("first line = %+v", lines[0])
48 }
49 if lines[2].Kind != "del" || lines[2].OldNo != 3 || lines[2].NewNo != 0 {
50 t.Fatalf("deleted line = %+v", lines[2])
51 }
52 if lines[3].Kind != "add" || lines[3].NewNo != 3 || lines[3].Text != "new line" {
53 t.Fatalf("added line = %+v", lines[3])
54 }
55 second := files[1]
56 if second.NewPath != "" || second.Label() != "notes.txt" {
57 t.Fatalf("deletion = %+v, label %q", second, second.Label())
58 }
59 if second.Removed != 2 {
60 t.Fatalf("deletion counts = −%d", second.Removed)
61 }
62 }
63
64 func TestParseDiffOfABinaryFile(t *testing.T) {
65 files := parseDiff(`diff --git a/logo.png b/logo.png
66 index 4444444..5555555 100644
67 Binary files a/logo.png and b/logo.png differ
68 `)
69 if len(files) != 1 || !files[0].Binary {
70 t.Fatalf("binary file not recognised: %+v", files)
71 }
72 }
73
74 func TestParseDiffOfANewFile(t *testing.T) {
75 files := parseDiff(`diff --git a/new.txt b/new.txt
76 new file mode 100644
77 index 0000000..6666666
78 --- /dev/null
79 +++ b/new.txt
80 @@ -0,0 +1,2 @@
81 +one
82 +two
83 `)
84 if len(files) != 1 {
85 t.Fatalf("files = %+v", files)
86 }
87 f := files[0]
88 if f.OldPath != "" || f.NewPath != "new.txt" || f.Added != 2 {
89 t.Fatalf("new file = %+v", f)
90 }
91 }
92
93 func TestParseDiffOfARename(t *testing.T) {
94 files := parseDiff(`diff --git a/old.md b/new.md
95 similarity index 100%
96 rename from old.md
97 rename to new.md
98 `)
99 if len(files) != 1 {
100 t.Fatalf("files = %+v", files)
101 }
102 // A pure rename has no ---/+++ lines, so both paths are empty and the file is
103 // shown under the name git gave it in the diff header.
104 if files[0].OldPath != "" || files[0].NewPath != "" {
105 t.Fatalf("rename paths = %+v", files[0])
106 }
107 }
108
109 func TestParseDiffIgnoresPlumbing(t *testing.T) {
110 files := parseDiff(`diff --git a/app.go b/app.go
111 index aaa..bbb 100644
112 --- a/app.go
113 +++ b/app.go
114 @@ -10,3 +10,3 @@ func main() {
115 ctx := context.Background()
116 - run(ctx)
117 + run(ctx, cfg)
118 `)
119 got := files[0].Hunks[0].Lines
120 for _, l := range got {
121 if strings.HasPrefix(l.Text, "index aaa") {
122 t.Fatalf("plumbing leaked into the rendered lines: %+v", got)
123 }
124 }
125 if got[1].OldNo != 11 || got[2].NewNo != 11 {
126 t.Fatalf("line numbers = %+v", got)
127 }
128 }
129
130 func TestParseDiffToleratesATruncatedPatch(t *testing.T) {
131 // A cut patch ends mid-hunk: it must render what it has, not panic or claim
132 // the missing lines were unchanged.
133 files := parseDiff(`diff --git a/big.txt b/big.txt
134 --- a/big.txt
135 +++ b/big.txt
136 @@ -1,2 +1,2 @@
137 - first
138 + second`)
139 if len(files) != 1 {
140 t.Fatalf("files = %+v", files)
141 }
142 if files[0].Added != 1 || files[0].Removed != 1 {
143 t.Fatalf("counts = %+v", files[0])
144 }
145 }
146
147 func TestDiffSummary(t *testing.T) {
148 added, removed := diffSummary(parseDiff(patchTwoFiles))
149 if added != 2 || removed != 3 {
150 t.Fatalf("summary = +%d −%d, want +2 −3", added, removed)
151 }
152 }