Every changed file is listed on MR, commit and compare pages. Generated files (lockfiles, vendor/, node_modules/, minified or generated code) and files over 2,000 changed lines render collapsed, with no rows.
"load diff" fetches that one file in place (one request, one file read, no scroll jump). Without JS, ?file=i reloads the page with the file spliced in.
One-file loads are capped at 8 MiB and 50,000 changed lines; beyond, the file shows a raw link and takes no line comment. Line comments accept any line the page can load.
Only changed lines count against the diff line limits (context lines no longer do).
API/CLI: files gain omitted (large, budget, unavailable) via ermite-api v0.11.0; truncated keeps its meaning.
static/js/lazy.js holds the shared lazy loader for MR, commit and compare pages.
Depends on !13: will be rebased after it merges (conflicts expected in mr.go, mr.js and goldens).
Checks: go build/vet/test, node --test (170), templ -check, Chromium check of load diff, scroll, focus, Enter, no-JS and idle mutations.
@@ -63,6 +63,14 @@ In `mr show --json`, `mergeability` holds `state`, `behind`, `behind_capped`, `c
63
63
64
64
In the web merge box a mode that is not possible is disabled with its reason, and the Merge button too when no mode is possible. A merge the server refuses (target moved, conflict, branch moved) shows the page again with the reason in the box.
65
65
66
+
## Large diffs on the web
67
+
68
+
The merge request, commit and compare pages list every changed file. Generated files (lockfiles, `vendor/`, `node_modules/`, minified or generated code) and large files (over 2,000 changed lines) are collapsed: the header and the counts show, the lines do not. "load diff" fetches that one file in place, in full, and you can comment on its lines. Without scripts the link reloads the page with that file loaded. After the first 25 files of a merge request, the other files load as you scroll.
69
+
70
+
A commit page reads the hunks of its first 300 files with the page; the other files are listed collapsed and load one at a time. A file that cannot be shown in the browser, over 8 MiB of text or over 50,000 changed lines when loaded alone, has no "load diff": the page links to its raw blob instead, and its lines take no comment.
71
+
72
+
The API and `mr show --diff --json` keep their limits (300 files, 2,000 changed lines per file, 20,000 in total, 8 MiB). A file read without its hunks has `truncated` set and `omitted` giving the reason: `large`, `budget` (the earlier files used the limits) or `unavailable` (over 8 MiB of text, or a line over 64 KiB, even alone). Files past the 300th are not listed and `diff_truncated` is set.
73
+
66
74
## Notes
67
75
68
76
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.
@@ -63,6 +63,14 @@ In `mr show --json`, `mergeability` holds `state`, `behind`, `behind_capped`, `c
63
63
64
In the web merge box a mode that is not possible is disabled with its reason, and the Merge button too when no mode is possible. A merge the server refuses (target moved, conflict, branch moved) shows the page again with the reason in the box.
64
In the web merge box a mode that is not possible is disabled with its reason, and the Merge button too when no mode is possible. A merge the server refuses (target moved, conflict, branch moved) shows the page again with the reason in the box.
65
65
66
+
## Large diffs on the web
67
+
68
+
The merge request, commit and compare pages list every changed file. Generated files (lockfiles, `vendor/`, `node_modules/`, minified or generated code) and large files (over 2,000 changed lines) are collapsed: the header and the counts show, the lines do not. "load diff" fetches that one file in place, in full, and you can comment on its lines. Without scripts the link reloads the page with that file loaded. After the first 25 files of a merge request, the other files load as you scroll.
69
+
70
+
A commit page reads the hunks of its first 300 files with the page; the other files are listed collapsed and load one at a time. A file that cannot be shown in the browser, over 8 MiB of text or over 50,000 changed lines when loaded alone, has no "load diff": the page links to its raw blob instead, and its lines take no comment.
71
+
72
+
The API and `mr show --diff --json` keep their limits (300 files, 2,000 changed lines per file, 20,000 in total, 8 MiB). A file read without its hunks has `truncated` set and `omitted` giving the reason: `large`, `budget` (the earlier files used the limits) or `unavailable` (over 8 MiB of text, or a line over 64 KiB, even alone). Files past the 300th are not listed and `diff_truncated` is set.
73
+
66
## Notes
74
## Notes
67
75
68
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.
76
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.
// readDiff parses the output of diff-tree --raw --numstat -p.
91
-
//
92
-
//hexcheck:ignore suspicious-business-logic-in-adapter streams raw, numstat and patch sections; domain.DiffBudget decides window and hunk eligibility and accounts retained lines and bytes; adapter only skips sections and stops the process
if why := p.budget.ChargeLine(len(text), changed); why != domain.OmitNone {
470
+
returnnil, lines, why, nil
471
+
}
472
+
if lines > max {
473
+
returnnil, lines, domain.OmitLarge, nil
466
474
}
467
475
cur.Lines = append(cur.Lines, l)
468
476
}
@@ -88,8 +88,6 @@ type rawEntry struct {
88
}
88
}
89
89
90
// readDiff parses the output of diff-tree --raw --numstat -p.
90
// readDiff parses the output of diff-tree --raw --numstat -p.
91
-
//
92
-
//hexcheck:ignore suspicious-business-logic-in-adapter streams raw, numstat and patch sections; domain.DiffBudget decides window and hunk eligibility and accounts retained lines and bytes; adapter only skips sections and stops the process
// diffNote says what is missing from an incomplete diff, "" for a complete one.
190
-
func diffNote(d domain.Diff, shown, total int) string {
191
-
if !d.Truncated {
192
+
// diffNote says what is missing from an incomplete diff, "" for a complete one:
193
+
// files too large for the browser at all. Collapsed files say so on their own
194
+
// line and load on their own; every changed file is listed.
195
+
func diffNote(d domain.Diff) string {
196
+
gone := 0
197
+
for _, f := range d.Files {
198
+
if f.Omitted == domain.OmitUnavailable {
199
+
gone++
200
+
}
201
+
}
202
+
if gone == 0 {
192
203
return""
193
204
}
194
-
var parts []string
195
-
if shown < total {
196
-
parts = append(parts, plural(total-shown, "file is", "files are")+" not listed")
205
+
return"this diff is incomplete: " + plural(gone, "file is", "files are") + " too large to show. the totals above count everything; clone the repository to read the rest."
206
+
}
207
+
208
+
// commitDiff reads a commit for its page (and the compare page): every file
209
+
// listed, the hunks of the first files read, generated files and files past
210
+
// the window collapsed (ports.GetCommitOptions.Page). With ?file=i (the link
211
+
// of a page without scripts) file i is read in full and replaces its entry.
212
+
func (s *Server) commitDiff(ctx context.Context, r *http.Request, repo domain.RepoName, rev string) (domain.CommitDetail, error) {
// diffNote says what is missing from an incomplete diff, "" for a complete one.
192
+
// diffNote says what is missing from an incomplete diff, "" for a complete one:
190
-
func diffNote(d domain.Diff, shown, total int) string {
193
+
// files too large for the browser at all. Collapsed files say so on their own
191
-
if !d.Truncated {
194
+
// line and load on their own; every changed file is listed.
195
+
func diffNote(d domain.Diff) string {
196
+
gone := 0
197
+
for _, f := range d.Files {
198
+
if f.Omitted == domain.OmitUnavailable {
199
+
gone++
200
+
}
201
+
}
202
+
if gone == 0 {
192
return""
203
return""
193
}
204
}
194
-
var parts []string
205
+
return"this diff is incomplete: " + plural(gone, "file is", "files are") + " too large to show. the totals above count everything; clone the repository to read the rest."
195
-
if shown < total {
206
+
}
196
-
parts = append(parts, plural(total-shown, "file is", "files are")+" not listed")
207
+
208
+
// commitDiff reads a commit for its page (and the compare page): every file
209
+
// listed, the hunks of the first files read, generated files and files past
210
+
// the window collapsed (ports.GetCommitOptions.Page). With ?file=i (the link
211
+
// of a page without scripts) file i is read in full and replaces its entry.
212
+
func (s *Server) commitDiff(ctx context.Context, r *http.Request, repo domain.RepoName, rev string) (domain.CommitDetail, error) {
// fileLinks says where the collapsed files of a diff are loaded from.
471
+
type fileLinks struct {
472
+
// src is the address of the fragment route of the diff ("" when files
473
+
// cannot be loaded in place); one file is src plus one=1&from=i.
474
+
src string
475
+
// from is the window of the page, kept in the links of a page without
476
+
// scripts.
477
+
from int
478
+
// repo and rev name the raw blob of a file that cannot be shown.
479
+
repo, rev string
480
+
}
481
+
482
+
// oneFileSrc is the address of the fragment holding file i alone.
483
+
func oneFileSrc(src string, i int) string {
484
+
sep := "?"
485
+
if strings.Contains(src, "?") {
486
+
sep = "&"
487
+
}
488
+
return src + sep + "one=1&from=" + itoa(i)
489
+
}
490
+
491
+
// collapseFiles gives the collapsed files of a diff their load address, and
492
+
// the files too large to show a link to their raw blob. Nothing is read.
493
+
//
494
+
//hexcheck:ignore suspicious-business-logic-in-adapter only maps each file's domain omission (Omitted.Collapsed) to a load link, a ?file= anchor or a raw link; it decides nothing and reads nothing
495
+
func collapseFiles(out []DiffFile, files []domain.FileDiff, l fileLinks) {
496
+
for i, f := range files {
497
+
if i >= len(out) {
498
+
break
499
+
}
500
+
switch {
501
+
case f.Omitted.Collapsed():
502
+
if l.src != "" {
503
+
out[i].Src = oneFileSrc(l.src, i)
504
+
}
505
+
q := "?file=" + itoa(i)
506
+
if l.from > 0 {
507
+
q = "?from=" + itoa(l.from) + "&file=" + itoa(i)
508
+
}
509
+
out[i].Href = q + "#" + out[i].ID
510
+
default:
511
+
out[i].Raw = rawLink(f, l)
512
+
}
513
+
}
514
+
}
515
+
516
+
// rawLink is the address of the raw blob of a file too large to show, "" for
517
+
// any other file.
518
+
func rawLink(f domain.FileDiff, l fileLinks) string {
// fileLinks says where the collapsed files of a diff are loaded from.
471
+
type fileLinks struct {
472
+
// src is the address of the fragment route of the diff ("" when files
473
+
// cannot be loaded in place); one file is src plus one=1&from=i.
474
+
src string
475
+
// from is the window of the page, kept in the links of a page without
476
+
// scripts.
477
+
from int
478
+
// repo and rev name the raw blob of a file that cannot be shown.
479
+
repo, rev string
480
+
}
481
+
482
+
// oneFileSrc is the address of the fragment holding file i alone.
483
+
func oneFileSrc(src string, i int) string {
484
+
sep := "?"
485
+
if strings.Contains(src, "?") {
486
+
sep = "&"
487
+
}
488
+
return src + sep + "one=1&from=" + itoa(i)
489
+
}
490
+
491
+
// collapseFiles gives the collapsed files of a diff their load address, and
492
+
// the files too large to show a link to their raw blob. Nothing is read.
493
+
//
494
+
//hexcheck:ignore suspicious-business-logic-in-adapter only maps each file's domain omission (Omitted.Collapsed) to a load link, a ?file= anchor or a raw link; it decides nothing and reads nothing
495
+
func collapseFiles(out []DiffFile, files []domain.FileDiff, l fileLinks) {
496
+
for i, f := range files {
497
+
if i >= len(out) {
498
+
break
499
+
}
500
+
switch {
501
+
case f.Omitted.Collapsed():
502
+
if l.src != "" {
503
+
out[i].Src = oneFileSrc(l.src, i)
504
+
}
505
+
q := "?file=" + itoa(i)
506
+
if l.from > 0 {
507
+
q = "?from=" + itoa(l.from) + "&file=" + itoa(i)
508
+
}
509
+
out[i].Href = q + "#" + out[i].ID
510
+
default:
511
+
out[i].Raw = rawLink(f, l)
512
+
}
513
+
}
514
+
}
515
+
516
+
// rawLink is the address of the raw blob of a file too large to show, "" for
517
+
// any other file.
518
+
func rawLink(f domain.FileDiff, l fileLinks) string {