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

395 commit(s) on main

update readme Keenan Nemetz · 22m ago 32290114
history blame raw 7.5 KiB
1package git
2
3// The per-file commit view reads the name list and then one file's patch. These
4// tests pin the two together, because a list that disagrees with the patch is a
5// tree row that opens nothing.
6
7import (
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strings"
12 "testing"
13)
14
15// changeRepo builds a repository whose second commit adds, modifies, deletes and
16// renames, with a space and a non-ASCII byte in the renamed path so git's quoting
17// is exercised.
18func changeRepo(t *testing.T) (*Repo, string) {
19 t.Helper()
20 dir := t.TempDir()
21 run := func(args ...string) {
22 t.Helper()
23 cmd := exec.Command("git", args...)
24 cmd.Dir = dir
25 cmd.Env = append(os.Environ(),
26 "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
27 "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.com",
28 "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.com",
29 // Without this a platform can quote every non-ASCII path into an
30 // escape sequence, and the test would be reading a different format.
31 "GIT_CONFIG_COUNT=1", "GIT_CONFIG_KEY_0=core.quotepath", "GIT_CONFIG_VALUE_0=false")
32 if out, err := cmd.CombinedOutput(); err != nil {
33 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, out)
34 }
35 }
36 write := func(name, content string) {
37 t.Helper()
38 if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
39 t.Fatal(err)
40 }
41 }
42 run("init", "-q", "-b", "main")
43 write("readme.md", "v1\n")
44 write("keep.txt", "keep\n")
45 write("old name.txt", "moved\n")
46 run("add", "-A")
47 run("commit", "-qm", "first")
48
49 write("readme.md", "v1\nv2\n")
50 if err := os.MkdirAll(filepath.Join(dir, "docs"), 0o755); err != nil {
51 t.Fatal(err)
52 }
53 write("docs/guide.md", "a guide\n")
54 if err := os.Remove(filepath.Join(dir, "keep.txt")); err != nil {
55 t.Fatal(err)
56 }
57 run("mv", "old name.txt", "nëw name.txt")
58 run("add", "-A")
59 run("commit", "-qm", "second")
60 return open(t, dir), "HEAD"
61}
62
63// inventoryRepo builds one commit whose diff has every shape the inventory has
64// to report: a modification, an addition, a deletion, a rename and a binary file.
65func inventoryRepo(t *testing.T) (*Repo, string) {
66 t.Helper()
67 dir := t.TempDir()
68 run := func(args ...string) {
69 t.Helper()
70 cmd := exec.Command("git", args...)
71 cmd.Dir = dir
72 cmd.Env = append(os.Environ(),
73 "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
74 "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.com",
75 "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.com")
76 if out, err := cmd.CombinedOutput(); err != nil {
77 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, out)
78 }
79 }
80 write := func(name, content string) {
81 t.Helper()
82 if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
83 t.Fatal(err)
84 }
85 }
86 run("init", "-q", "-b", "main")
87 write("readme.md", "one\n")
88 write("gone.txt", "gone\n")
89 write("old name.txt", "moved\n")
90 write("blob.bin", "\x00\x01")
91 run("add", "-A")
92 run("commit", "-qm", "first")
93
94 write("readme.md", "one\ntwo\n")
95 write("added.txt", "new\nnew\n")
96 if err := os.Remove(filepath.Join(dir, "gone.txt")); err != nil {
97 t.Fatal(err)
98 }
99 run("mv", "old name.txt", "new name.txt")
100 write("blob.bin", "\x00\x01\x02")
101 run("add", "-A")
102 run("commit", "-qm", "second")
103 return open(t, dir), "HEAD"
104}
105
106// TestDiffInventoryCountsEveryFile: the inventory is what a changed-file tree and
107// a landing summary read, so it has to name every file with the status the tree
108// marks and the counts the summary adds up — without generating a patch.
109func TestDiffInventoryCountsEveryFile(t *testing.T) {
110 t.Parallel()
111 r, rev := inventoryRepo(t)
112
113 changes, err := r.DiffInventory(rev+"~1...HEAD", "")
114 if err != nil {
115 t.Fatalf("DiffInventory: %v", err)
116 }
117 if len(changes) != 5 {
118 t.Fatalf("DiffInventory returned %d changes, want 5: %+v", len(changes), changes)
119 }
120 got := map[string]PathChange{}
121 for _, c := range changes {
122 got[pathLabel(c)] = c
123 }
124 for _, want := range []PathChange{
125 {Status: "M", Old: "readme.md", New: "readme.md", Added: 1, Removed: 0},
126 {Status: "A", Old: "", New: "added.txt", Added: 2, Removed: 0},
127 {Status: "D", Old: "gone.txt", New: "", Added: 0, Removed: 1},
128 {Status: "R", Old: "old name.txt", New: "new name.txt"},
129 {Status: "M", Old: "blob.bin", New: "blob.bin", Binary: true},
130 } {
131 have, ok := got[pathLabel(want)]
132 if !ok {
133 t.Fatalf("DiffInventory has no %+v: %+v", want, changes)
134 }
135 if have != want {
136 t.Fatalf("%s = %+v, want %+v", pathLabel(want), have, want)
137 }
138 }
139
140 // The two-revision form is what a merged request reads: the tip it landed on
141 // and the change that landed are commits, not a range.
142 base := strings.TrimSpace(mustGit(t, r, "rev-parse", "HEAD~1"))
143 pair, err := r.DiffInventory(base, "HEAD")
144 if err != nil {
145 t.Fatalf("DiffInventory (two revisions): %v", err)
146 }
147 if len(pair) != len(changes) {
148 t.Fatalf("two-revision inventory has %d files, want %d", len(pair), len(changes))
149 }
150}
151
152func TestShowNameStatusNamesEveryPathThePatchWillPrint(t *testing.T) {
153 r, rev := changeRepo(t)
154
155 changes, err := r.ShowNameStatus(rev)
156 if err != nil {
157 t.Fatalf("ShowNameStatus: %v", err)
158 }
159 got := map[string]PathChange{}
160 for _, c := range changes {
161 got[c.New+c.Old] = c
162 }
163 if len(changes) != 4 {
164 t.Fatalf("ShowNameStatus returned %d changes, want 4: %+v", len(changes), changes)
165 }
166 for _, want := range []PathChange{
167 {Status: "A", Old: "", New: "docs/guide.md"},
168 {Status: "D", Old: "keep.txt", New: ""},
169 {Status: "R", Old: "old name.txt", New: "nëw name.txt"},
170 {Status: "M", Old: "readme.md", New: "readme.md"},
171 } {
172 c, ok := got[want.New+want.Old]
173 if !ok {
174 t.Fatalf("ShowNameStatus has no %+v: %+v", want, changes)
175 }
176 if c.Status != want.Status {
177 t.Fatalf("%s status = %q, want %q", want.New, c.Status, want.Status)
178 }
179 }
180}
181
182func TestShowPatchPathReadsOneFile(t *testing.T) {
183 r, rev := changeRepo(t)
184
185 patch, err := r.ShowPatchPath(rev, []string{"readme.md"})
186 if err != nil {
187 t.Fatalf("ShowPatchPath: %v", err)
188 }
189 if !strings.Contains(patch, "+v2") {
190 t.Fatalf("the patch does not hold the change:\n%s", patch)
191 }
192 if strings.Contains(patch, "a guide") {
193 t.Fatalf("the patch holds another file:\n%s", patch)
194 }
195
196 // A rename needs both paths: with only the destination named, git reports what
197 // is left as a new file and the view would lose the rename.
198 onlyNew, err := r.ShowPatchPath(rev, []string{"nëw name.txt"})
199 if err != nil {
200 t.Fatalf("ShowPatchPath (new only): %v", err)
201 }
202 if strings.Contains(onlyNew, "rename from") {
203 t.Fatalf("one path should not have been enough for a rename:\n%s", onlyNew)
204 }
205 both, err := r.ShowPatchPath(rev, []string{"old name.txt", "nëw name.txt"})
206 if err != nil {
207 t.Fatalf("ShowPatchPath (both): %v", err)
208 }
209 if !strings.Contains(both, "rename from old name.txt") || !strings.Contains(both, "similarity index 100%") {
210 t.Fatalf("the patch is not the whole rename:\n%s", both)
211 }
212
213 // A root commit has no parent to diff against; the file is an addition.
214 root := strings.TrimSpace(mustGit(t, r, "rev-list", "--max-parents=0", "HEAD"))
215 changes, err := r.ShowNameStatus(root)
216 if err != nil {
217 t.Fatalf("ShowNameStatus (root): %v", err)
218 }
219 if len(changes) != 3 {
220 t.Fatalf("root commit lists %d files, want 3: %+v", len(changes), changes)
221 }
222 for _, c := range changes {
223 if c.Status != "A" || c.Old != "" {
224 t.Fatalf("root commit change = %+v, want an addition", c)
225 }
226 }
227}
228
229// mustGit runs one read command through the repository under test.
230func mustGit(t *testing.T, r *Repo, args ...string) string {
231 t.Helper()
232 out, errs, err := r.run(nil, args...)
233 if err != nil {
234 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, errs)
235 }
236 return string(out)
237}
238