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/issues_changes_test.go +49

@@ -700,6 +700,55 @@ func TestIssueAndChangeListsReturnFragmentsForHtmx(t *testing.T) {
700 700 }
701 701 }
702 702
703 // TestRequestFilesListsEveryChangedFile: the changed-file tree and the count come
704 // from the change's inventory, so a file larger than a page can render does not
705 // hide the files named after it. The patch the tab shows is still one file's.
706 func TestRequestFilesListsEveryChangedFile(t *testing.T) {
707 t.Parallel()
708 f := newFixture(t)
709 f.publish(f.repo)
710 base := workRepo(t, map[string]string{"readme.md": "v1\n"})
711 if _, errs, err := f.git(base, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
712 t.Fatalf("main push: %s", errs)
713 }
714 // a-big.txt sorts first and is larger than the 512 KiB that used to bound the
715 // patch the tree was parsed from, so the files after it were never listed.
716 files := map[string]string{"a-big.txt": strings.Repeat("x", 600<<10) + "\n"}
717 for _, name := range []string{"b.txt", "docs/c.txt", "z.txt"} {
718 files[name] = name + "\n"
719 }
720 feature, _ := f.cloneWithChange(t, files, "a big change")
721 if _, errs, err := f.git(feature, "push", f.sshURL(f.repo), "HEAD:refs/heads/feature"); err != nil {
722 t.Fatalf("feature push: %s", errs)
723 }
724 sess, cookie := f.browserSession(t, f.ownerID)
725 resp, body := f.browserPost(t, "/r/"+f.repo+"/requests", cookie, url.Values{
726 "csrf": {sess.CSRF}, "title": {"Every file"}, "body": {"body\n"},
727 "base": {"main"}, "head": {"feature"},
728 })
729 if resp.StatusCode != http.StatusSeeOther {
730 t.Fatalf("create change: %d\n%s", resp.StatusCode, body)
731 }
732 id := strings.TrimPrefix(resp.Header.Get("Location"), "/r/"+f.repo+"/requests/")
733
734 filesURL := "/r/" + f.repo + "/requests/" + id + "/files"
735 _, page := f.browserGet(t, filesURL, cookie)
736 if !strings.Contains(page, "4 changed") {
737 t.Errorf("the tree does not count every file:\n%.2000s", page)
738 }
739 for _, name := range []string{"a-big.txt", "b.txt", "docs/c.txt", "z.txt"} {
740 if !strings.Contains(page, filesURL+"/"+name) {
741 t.Errorf("the tree is missing %s:\n%.2000s", name, page)
742 }
743 }
744 // The open file is one file's patch, and it is whole.
745 _, page = f.browserGet(t, filesURL+"/z.txt", cookie)
746 if !strings.Contains(page, `<span class="path">z.txt</span>`) ||
747 !strings.Contains(page, `data-highlight-side="new">z.txt<`) {
748 t.Fatalf("the selected file has no patch:\n%.2000s", page)
749 }
750 }
751
703 752 func TestLineAnchoredReviewCommentFromTheWeb(t *testing.T) {
704 753 t.Parallel()
705 754 f := newFixture(t)