diff --git a/scm-ui/ui-components/src/repos/DiffExpander.test.ts b/scm-ui/ui-components/src/repos/DiffExpander.test.ts index b97609299e..8d2716de20 100644 --- a/scm-ui/ui-components/src/repos/DiffExpander.test.ts +++ b/scm-ui/ui-components/src/repos/DiffExpander.test.ts @@ -199,7 +199,7 @@ describe("with hunks the diff expander", () => { }); it("should return a really bix number for the expand bottom range of the last hunk", () => { - expect(diffExpander.getHunk(3).maxExpandBottomRange).toBeGreaterThan(99999); + expect(diffExpander.getHunk(3).maxExpandBottomRange).toBe(-1); }); it("should expand hunk with new line from api client at the bottom", async () => { expect(diffExpander.getHunk(1).hunk.changes.length).toBe(7); @@ -251,6 +251,18 @@ describe("with hunks the diff expander", () => { await fetchMock.flush(true); expect(newFile.hunks[3].fullyExpanded).toBe(true); }); + it("should set end to -1 if requested to expand to the end", async () => { + fetchMock.get( + "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start=40&end=-1", + "new line 40\nnew line 41\nnew line 42" + ); + let newFile; + diffExpander.getHunk(3).expandBottom(-1, file => { + newFile = file; + }); + await fetchMock.flush(true); + expect(newFile.hunks[3].fullyExpanded).toBe(true); + }); }); describe("for a new file with text input the diff expander", () => { diff --git a/scm-ui/ui-components/src/repos/DiffExpander.ts b/scm-ui/ui-components/src/repos/DiffExpander.ts index 3dfd91a738..676569e95e 100644 --- a/scm-ui/ui-components/src/repos/DiffExpander.ts +++ b/scm-ui/ui-components/src/repos/DiffExpander.ts @@ -61,7 +61,7 @@ class DiffExpander { if (this.file.type === "add" || this.file.type === "delete") { return 0; } else if (n === this.file!.hunks!.length - 1) { - return this.file!.hunks![this.file!.hunks!.length - 1].fullyExpanded ? 0 : Number.MAX_SAFE_INTEGER; + return this.file!.hunks![this.file!.hunks!.length - 1].fullyExpanded ? 0 : -1; } return this.minLineNumber(n + 1) - this.maxLineNumber(n) - 1; }; @@ -78,9 +78,10 @@ class DiffExpander { }; expandBottom = (n: number, count: number, callback: (newFile: File) => void) => { + const maxExpandBottomRange = this.computeMaxExpandBottomRange(n); const lineRequestUrl = this.file._links.lines.href .replace("{start}", this.maxLineNumber(n)) - .replace("{end}", this.maxLineNumber(n) + Math.min(count, this.computeMaxExpandBottomRange(n))); + .replace("{end}", count > 0 ? this.maxLineNumber(n) + Math.min(count, maxExpandBottomRange > 0? maxExpandBottomRange:Number.MAX_SAFE_INTEGER) : -1); apiClient .get(lineRequestUrl) .then(response => response.text()) @@ -156,7 +157,7 @@ class DiffExpander { oldLines: hunk.oldLines + lines.length, newLines: hunk.newLines + lines.length, changes: newChanges, - fullyExpanded: lines.length < requestedLines + fullyExpanded: requestedLines < 0 || lines.length < requestedLines }; const newHunks: Hunk[] = []; this.file.hunks.forEach((oldHunk: Hunk, i: number) => { diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index 88f62cfecf..5781f3f284 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -212,7 +212,7 @@ class DiffFile extends React.Component { }; createLastHunkFooter = (expandableHunk: ExpandableHunk) => { - if (expandableHunk.maxExpandBottomRange > 0) { + if (expandableHunk.maxExpandBottomRange != 0) { return ( diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java index 47e0de16d1..ed5ebe6e57 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java @@ -115,10 +115,16 @@ public class ContentResource { } private StreamingOutput createStreamingOutput(String namespace, String name, String revision, String path, Integer start, Integer end) { + Integer effectiveEnd; + if (end != null && end < 0) { + effectiveEnd = null; + } else { + effectiveEnd = end; + } return os -> { OutputStream sourceOut; - if (start != null || end != null) { - sourceOut = new LineFilteredOutputStream(os, start, end); + if (start != null || effectiveEnd != null) { + sourceOut = new LineFilteredOutputStream(os, start, effectiveEnd); } else { sourceOut = os; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java index c009751195..7cb067a965 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java @@ -109,6 +109,18 @@ public class ContentResourceTest { assertEquals("line 2\nline 3\n", baos.toString()); } + @Test + public void shouldNotLimitOutputWhenEndLessThanZero() throws Exception { + mockContent("file", "line 1\nline 2\nline 3\nline 4".getBytes()); + + Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "file", 1, -1); + assertEquals(200, response.getStatus()); + + ByteArrayOutputStream baos = readOutputStream(response); + + assertEquals("line 2\nline 3\nline 4", baos.toString()); + } + @Test public void shouldHandleMissingFile() { Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "doesNotExist", null, null);