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/web/commit.go +15−19
| @@ -12,11 +12,6 @@ import ( | |||
| 12 | 12 | "github.com/rodent-software/vole/core/git" | |
| 13 | 13 | ) | |
| 14 | 14 | ||
| 15 | // diffCap bounds what one file's view renders. A patch is parsed in memory and | ||
| 16 | // shown in full; a generated 50 MiB diff is not a review, so it is cut and | ||
| 17 | // labelled as cut. | ||
| 18 | const diffCap = 512 << 10 | ||
| 19 | |||
| 20 | 15 | // commitView is the commit page: the metadata, the changed-file tree, and the | |
| 21 | 16 | // pane the selected file's diff renders in. | |
| 22 | 17 | type commitView struct { | |
| @@ -34,11 +29,10 @@ type commitView struct { | |||
| 34 | 29 | // commitMainView is the diff pane, both as the page's main column and as the | |
| 35 | 30 | // HTMX fragment a tree click swaps in. | |
| 36 | 31 | type commitMainView struct { | |
| 37 | Path string | ||
| 38 | Files []diffFile | ||
| 39 | Added int | ||
| 40 | Removed int | ||
| 41 | Truncated bool | ||
| 32 | Path string | ||
| 33 | Files []diffFile | ||
| 34 | Added int | ||
| 35 | Removed int | ||
| 42 | 36 | // A commit page is read-only, so a diff line has nowhere to attach a review | |
| 43 | 37 | // comment; the diff component reads this and draws no comment control. | |
| 44 | 38 | LineCommentURL string | |
| @@ -122,11 +116,10 @@ func commitDiff(g *git.Repo, sha, selected string, files []diffFile, chosen int) | |||
| 122 | 116 | return main, nil | |
| 123 | 117 | } | |
| 124 | 118 | f := files[chosen] | |
| 125 | patch, truncated, err := g.ShowPatchPath(sha, f.pathspec(), diffCap) | ||
| 119 | patch, err := g.ShowPatchPath(sha, f.pathspec()) | ||
| 126 | 120 | if err != nil { | |
| 127 | 121 | return main, err | |
| 128 | 122 | } | |
| 129 | main.Truncated = truncated | ||
| 130 | 123 | for _, p := range parseDiff(patch) { | |
| 131 | 124 | if diffFilePath(p) == selected { | |
| 132 | 125 | main.Files = []diffFile{p} | |
| @@ -134,9 +127,9 @@ func commitDiff(g *git.Repo, sha, selected string, files []diffFile, chosen int) | |||
| 134 | 127 | } | |
| 135 | 128 | } | |
| 136 | 129 | if main.Files == nil { | |
| 137 | // A patch git printed nothing for (a mode change, or a cut that stopped | ||
| 138 | // before this file) still belongs in the pane: the tree lists it, so the | ||
| 139 | // pane can never pretend it is not there. | ||
| 130 | // A patch git printed nothing for (a mode change, for instance) still | ||
| 131 | // belongs in the pane: the tree lists it, so the pane can never pretend | ||
| 132 | // it is not there. | ||
| 140 | 133 | f.Language = prismLanguage(f.Label()) | |
| 141 | 134 | main.Files = []diffFile{f} | |
| 142 | 135 | } | |
| @@ -144,13 +137,16 @@ func commitDiff(g *git.Repo, sha, selected string, files []diffFile, chosen int) | |||
| 144 | 137 | return main, nil | |
| 145 | 138 | } | |
| 146 | 139 | ||
| 147 | // changedFiles turns a commit's name-status lines into the rows the tree and the | ||
| 148 | // pane need: the same shape a parsed diff produces, so the status mark and the | ||
| 149 | // file's label are derived the same way whichever read built them. | ||
| 140 | // changedFiles turns a name-status list, or a change's inventory, into the rows a | ||
| 141 | // changed-file tree and its pane need: the same shape a parsed diff produces, so | ||
| 142 | // the status mark, the label and the line counts are derived the same way | ||
| 143 | // whichever read built them. | ||
| 150 | 144 | func changedFiles(changes []git.PathChange) []diffFile { | |
| 151 | 145 | files := make([]diffFile, 0, len(changes)) | |
| 152 | 146 | for _, c := range changes { | |
| 153 | files = append(files, diffFile{OldPath: c.Old, NewPath: c.New}) | ||
| 147 | f := diffFile{OldPath: c.Old, NewPath: c.New, Added: c.Added, Removed: c.Removed, Binary: c.Binary} | ||
| 148 | f.Language = prismLanguage(f.Label()) | ||
| 149 | files = append(files, f) | ||
| 154 | 150 | } | |
| 155 | 151 | return files | |
| 156 | 152 | } | |