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.
Read a change's file list without its patch, and never cut a diff
f64c77e4950c1759f336d3bc1849ead86e8b8ebf · Keenan Nemetz · 8d ago · parent 11720250
Read a change's file list without its patch, and never cut a diff The review tree, the file count and the landing summary were all parsed out of one `git diff` capped at 512 KiB. When a change carries a large file the cut lands inside it, and every file named after the cut is missing from the tree: reviewing #143, a 546 KB deletion hid the 296 files that came after it while the tab still said "6 changed". The cap existed to stop a page rendering every hunk, but the pages it guarded already render one file at a time. The inventory and the patch are separate reads now. DiffInventory runs `git diff --raw --numstat -z`, which names every file with its status and line counts in one walk of the trees and generates no hunks; the changed-file tree, the counts and the landing summary read that. The Files tab then asks for the one file it shows with DiffPaths, and the commit page keeps its per-file ShowPatchPath. Nothing is capped by bytes: a page is as large as the file it shows, and the inventory is the whole change however large it is. Compare still renders every file of a range at once, and is uncapped too, so a large comparison is one large page. Making that per-file is the next step if it turns out to matter. TestDiffInventoryCountsEveryFile covers the statuses, counts, renames and binary files the inventory has to report; TestRequestFilesListsEveryChangedFile is the regression for the tree that lost its files, and the commit page test now asserts the end of a large patch instead of its cut.
server/commit_test.go +17−12
| @@ -127,20 +127,22 @@ func TestCommitPageOpensOneFileAtATime(t *testing.T) { | |||
| 127 | 127 | } | |
| 128 | 128 | } | |
| 129 | 129 | ||
| 130 | // TestCommitPageCutsOneFilesPatch: the cap is what stops a generated diff from | ||
| 131 | // being rendered in full, and it applies to the file that is open rather than to | ||
| 132 | // the whole commit. | ||
| 133 | func TestCommitPageCutsOneFilesPatch(t *testing.T) { | ||
| 130 | // TestCommitPageShowsTheWholeOfOneFilesPatch: the page renders one file's patch, | ||
| 131 | // not the whole commit's, so the file that is open is shown whole. A byte cut | ||
| 132 | // would hide the end of a large file and, worse, every file named after it. | ||
| 133 | func TestCommitPageShowsTheWholeOfOneFilesPatch(t *testing.T) { | ||
| 134 | 134 | t.Parallel() | |
| 135 | 135 | f := newFixture(t) | |
| 136 | 136 | f.publish(f.repo) | |
| 137 | 137 | ||
| 138 | // One line longer than the 512 KiB cap, so the file that is open is cut. | ||
| 138 | // The file is larger than the 512 KiB that used to bound one file's patch, | ||
| 139 | // and it ends with a marker the cut would have dropped. | ||
| 140 | big := strings.Repeat("x", 513<<10) + "END-OF-BIG-FILE" | ||
| 139 | 141 | work := workRepo(t, map[string]string{"small.txt": "one\n"}) | |
| 140 | 142 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | |
| 141 | 143 | t.Fatalf("initial push: %s", errs) | |
| 142 | 144 | } | |
| 143 | if err := os.WriteFile(filepath.Join(work, "big.txt"), []byte(strings.Repeat("x", 513<<10)+"\n"), 0o644); err != nil { | ||
| 145 | if err := os.WriteFile(filepath.Join(work, "big.txt"), []byte(big+"\n"), 0o644); err != nil { | ||
| 144 | 146 | t.Fatal(err) | |
| 145 | 147 | } | |
| 146 | 148 | if err := os.WriteFile(filepath.Join(work, "small.txt"), []byte("one\ntwo\n"), 0o644); err != nil { | |
| @@ -159,15 +161,18 @@ func TestCommitPageCutsOneFilesPatch(t *testing.T) { | |||
| 159 | 161 | commitURL := "/r/" + f.repo + "/commit/" + sha | |
| 160 | 162 | ||
| 161 | 163 | _, page := f.get(commitURL + "/big.txt") | |
| 162 | if !strings.Contains(page, "showing the first 512 KiB") { | ||
| 163 | t.Fatalf("the cut file is not labelled as cut:\n%.2000s", page) | ||
| 164 | if !strings.Contains(page, "END-OF-BIG-FILE") { | ||
| 165 | t.Fatalf("the open file's patch is not whole:\n%.2000s", page) | ||
| 164 | 166 | } | |
| 165 | // The small file is whole, and cutting the big one did not hide its row. | ||
| 166 | _, page = f.get(commitURL + "/small.txt") | ||
| 167 | 167 | if strings.Contains(page, "showing the first 512 KiB") { | |
| 168 | t.Fatalf("the small file was reported as cut:\n%s", page) | ||
| 168 | t.Fatalf("the file was reported as cut:\n%.2000s", page) | ||
| 169 | } | ||
| 170 | // The small file is whole too, and the big one did not hide its row. | ||
| 171 | _, page = f.get(commitURL + "/small.txt") | ||
| 172 | if !strings.Contains(page, `data-highlight-side="new">two<`) { | ||
| 173 | t.Fatalf("the small file has no patch:\n%.2000s", page) | ||
| 169 | 174 | } | |
| 170 | 175 | if !strings.Contains(page, commitURL+"/big.txt") { | |
| 171 | t.Fatalf("the cut file is missing from the tree:\n%s", page) | ||
| 176 | t.Fatalf("the big file is missing from the tree:\n%.2000s", page) | ||
| 172 | 177 | } | |
| 173 | 178 | } | |