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.

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/request_files.go +30−8

@@ -15,6 +15,7 @@ import (
15 15 "strconv"
16 16 "strings"
17 17
18 "github.com/rodent-software/vole/core/git"
18 19 "github.com/rodent-software/vole/forge/repo"
19 20 "github.com/rodent-software/vole/metadata"
20 21 )
@@ -87,6 +88,25 @@ func buildReviewTree(files []diffFile, active string) []*treeNode {
87 88 return root.Children
88 89 }
89 90
91 // reviewFileDiff reads the patch of the one file the pane shows, and only that
92 // file's. Reading every file's hunks to display one is what made a broad change
93 // expensive, and cutting the result to fit is what hid the files that came after
94 // the cut.
95 func reviewFileDiff(g *git.Repo, change reviewChange, f diffFile) ([]diffFile, error) {
96 patch, err := g.DiffPaths(change.From, change.To, f.pathspec())
97 if err != nil {
98 return nil, err
99 }
100 for _, p := range parseDiff(patch) {
101 if diffFilePath(p) == diffFilePath(f) {
102 return []diffFile{p}, nil
103 }
104 }
105 // A file git prints no patch for (a mode change, for instance) still belongs
106 // in the pane: the tree lists it, so the pane cannot pretend it is not there.
107 return []diffFile{f}, nil
108 }
109
90 110 // reviewMainView is what the Files pane renders, both as the full page's main
91 111 // column and as the HTMX fragment a tree click swaps in.
92 112 type reviewMainView struct {
@@ -96,7 +116,6 @@ type reviewMainView struct {
96 116 Files []diffFile
97 117 Added int
98 118 Removed int
99 Truncated bool
100 119 AlreadyMerged bool
101 120 LineCommentURL string
102 121 }
@@ -122,9 +141,9 @@ func (w *Web) RequestFiles(rw http.ResponseWriter, r *http.Request) {
122 141 w.notFound(rw, r)
123 142 return
124 143 }
125 files, truncated, alreadyMerged := w.requestDiff(m, req)
144 change := w.reviewDiff(m, req)
145 files := change.Files
126 146 comments := w.requestComments(m, req)
127 attachRequestComments(files, comments)
128 147
129 148 selected := r.PathValue("path")
130 149 if selected == "" && len(files) > 0 {
@@ -150,14 +169,17 @@ func (w *Web) RequestFiles(rw http.ResponseWriter, r *http.Request) {
150 169 Name: m.Name,
151 170 Request: req,
152 171 Path: active,
153 Truncated: truncated,
154 AlreadyMerged: alreadyMerged,
172 AlreadyMerged: change.Merged,
155 173 LineCommentURL: "/r/" + m.Name + "/requests/" + req.ID,
156 174 }
157 175 if chosen != nil {
158 main.Files = []diffFile{*chosen}
159 main.Added = chosen.Added
160 main.Removed = chosen.Removed
176 main.Files, err = reviewFileDiff(m.Git(), change, *chosen)
177 if err != nil {
178 w.serverError(rw, r, "cannot read that change: "+err.Error())
179 return
180 }
181 attachRequestComments(main.Files, comments)
182 main.Added, main.Removed = diffSummary(main.Files)
161 183 }
162 184 if htmxRequest(r) {
163 185 w.render(rw, "review_main", main, 0)