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.

Open one file at a time on the commit page

628d45239eb09af4e46c9dc779d30144e96c7e6f · Keenan Nemetz · 11d ago · parent 816cc543

Open one file at a time on the commit page

A commit page rendered every patch it contained, so a broad commit turned one
page load into every blob it touched. It now opens the file tree beside the diff
of a single file, the same shape a change request's Files tab uses: the tree is
built from `git show --name-status`, only the selected path's patch is read, and
a tree click swaps the pane over HTMX. `/commit/{sha}/{path}` deep-links a file
and a 512 KiB cap still bounds one file's patch.

The file tree became one widget for three surfaces rather than two, so its
context gained a Commit field and its href/target/status rules moved onto the
view (Branch, Diff, Href).

The pane's header is now one row: the file's name on the left and its line
counts on the right, with the separate "1 file changed" summary line dropped
when a single file fills the pane. The total-line remains for the multi-file
diff a compare renders.

core/git/commit_test.go +151

@@ -0,0 +1,151 @@
1 package 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
7 import (
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.
18 func 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 func TestShowNameStatusNamesEveryPathThePatchWillPrint(t *testing.T) {
64 r, rev := changeRepo(t)
65
66 changes, err := r.ShowNameStatus(rev)
67 if err != nil {
68 t.Fatalf("ShowNameStatus: %v", err)
69 }
70 got := map[string]PathChange{}
71 for _, c := range changes {
72 got[c.New+c.Old] = c
73 }
74 if len(changes) != 4 {
75 t.Fatalf("ShowNameStatus returned %d changes, want 4: %+v", len(changes), changes)
76 }
77 for _, want := range []PathChange{
78 {Status: "A", Old: "", New: "docs/guide.md"},
79 {Status: "D", Old: "keep.txt", New: ""},
80 {Status: "R", Old: "old name.txt", New: "nëw name.txt"},
81 {Status: "M", Old: "readme.md", New: "readme.md"},
82 } {
83 c, ok := got[want.New+want.Old]
84 if !ok {
85 t.Fatalf("ShowNameStatus has no %+v: %+v", want, changes)
86 }
87 if c.Status != want.Status {
88 t.Fatalf("%s status = %q, want %q", want.New, c.Status, want.Status)
89 }
90 }
91 }
92
93 func TestShowPatchPathReadsOneFile(t *testing.T) {
94 r, rev := changeRepo(t)
95
96 patch, truncated, err := r.ShowPatchPath(rev, []string{"readme.md"}, 1<<20)
97 if err != nil {
98 t.Fatalf("ShowPatchPath: %v", err)
99 }
100 if truncated {
101 t.Fatal("a small patch was reported as cut")
102 }
103 if !strings.Contains(patch, "+v2") {
104 t.Fatalf("the patch does not hold the change:\n%s", patch)
105 }
106 if strings.Contains(patch, "a guide") {
107 t.Fatalf("the patch holds another file:\n%s", patch)
108 }
109
110 // A rename needs both paths: with only the destination named, git reports what
111 // is left as a new file and the view would lose the rename.
112 onlyNew, _, err := r.ShowPatchPath(rev, []string{"nëw name.txt"}, 1<<20)
113 if err != nil {
114 t.Fatalf("ShowPatchPath (new only): %v", err)
115 }
116 if strings.Contains(onlyNew, "rename from") {
117 t.Fatalf("one path should not have been enough for a rename:\n%s", onlyNew)
118 }
119 both, _, err := r.ShowPatchPath(rev, []string{"old name.txt", "nëw name.txt"}, 1<<20)
120 if err != nil {
121 t.Fatalf("ShowPatchPath (both): %v", err)
122 }
123 if !strings.Contains(both, "rename from old name.txt") || !strings.Contains(both, "similarity index 100%") {
124 t.Fatalf("the patch is not the whole rename:\n%s", both)
125 }
126
127 // A root commit has no parent to diff against; the file is an addition.
128 root := strings.TrimSpace(mustGit(t, r, "rev-list", "--max-parents=0", "HEAD"))
129 changes, err := r.ShowNameStatus(root)
130 if err != nil {
131 t.Fatalf("ShowNameStatus (root): %v", err)
132 }
133 if len(changes) != 3 {
134 t.Fatalf("root commit lists %d files, want 3: %+v", len(changes), changes)
135 }
136 for _, c := range changes {
137 if c.Status != "A" || c.Old != "" {
138 t.Fatalf("root commit change = %+v, want an addition", c)
139 }
140 }
141 }
142
143 // mustGit runs one read command through the repository under test.
144 func mustGit(t *testing.T, r *Repo, args ...string) string {
145 t.Helper()
146 out, errs, err := r.run(nil, args...)
147 if err != nil {
148 t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, errs)
149 }
150 return string(out)
151 }