Fix an exception in the diff code for a corner case
This regression was introduced with I1e15767fc1. It resulted in an internal server error when the diff was requested for the old file name of a renamed file. It's actually not reasonable to request that diff but due to an issue in PolyGerrit, users now easily run into that case. The fix reverts the behavior for this case back to the previous one: The backend returns an empty diff result with only skipped lines, oblivious of the passed context and the presence of comments. Whether that behavior is *correct* is difficult to decide as it's hard to define what should be returned in that case. Actually, requesting the diff for the old file name (given that Gerrit identified a rename) is not a use case which makes sense. This change additionally adds two further tests which weren't covered previously. Users also reported a similar issue with PDF files. When trying to reproduce it locally, it didn't occur. However, the same PDF files were locally marked as binary whereas on a Gerrit server, on which the issue occurred, their content was shown as text on PolyGerrit. Given this and the similar exception, it's very likely that the fix of this change will also help for the PDF issue. Bug: Issue 9498 Bug: Issue 9557 Change-Id: I101d262c1566aa89a91a82394f51dab7c6e6311e
This commit is contained in:
@@ -17,6 +17,7 @@ package com.google.gerrit.extensions.common.testing;
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.IntegerSubject;
|
||||
import com.google.common.truth.IterableSubject;
|
||||
import com.google.common.truth.StringSubject;
|
||||
import com.google.common.truth.Subject;
|
||||
@@ -81,4 +82,10 @@ public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEnt
|
||||
ContentEntry contentEntry = actual();
|
||||
return Truth.assertThat(contentEntry.editB).named("intraline edits of 'b'");
|
||||
}
|
||||
|
||||
public IntegerSubject numberOfSkippedLines() {
|
||||
isNotNull();
|
||||
ContentEntry contentEntry = actual();
|
||||
return Truth.assertThat(contentEntry.skip).named("number of skipped lines");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,13 @@ class PatchScriptBuilder {
|
||||
int aSize = a.src.size();
|
||||
int bSize = b.src.size();
|
||||
|
||||
if (edits.isEmpty() && (aSize == 0 || bSize == 0)) {
|
||||
// The diff was requested for a file which was either added or deleted but which JGit doesn't
|
||||
// consider a file addition/deletion (e.g. requesting a diff for the old file name of a
|
||||
// renamed file looks like a deletion).
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Edit> lastEdit = getLast(edits);
|
||||
if (isNewlineAtEndDeleted()) {
|
||||
Optional<Edit> lastLineEdit = lastEdit.filter(edit -> edit.getEndA() == aSize);
|
||||
|
||||
@@ -32,6 +32,9 @@ import com.google.gerrit.acceptance.PushOneCommit.Result;
|
||||
import com.google.gerrit.common.RawInputUtil;
|
||||
import com.google.gerrit.extensions.api.changes.FileApi;
|
||||
import com.google.gerrit.extensions.api.changes.RebaseInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
||||
import com.google.gerrit.extensions.client.Comment;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeType;
|
||||
import com.google.gerrit.extensions.common.DiffInfo;
|
||||
@@ -2343,6 +2346,126 @@ public class RevisionDiffIT extends AbstractDaemonTest {
|
||||
assertThat(changedFiles.get(FILE_NAME)).linesDeleted().isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffOfUnmodifiedFileWithWholeFileContextReturnsFileContents() throws Exception {
|
||||
addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
|
||||
String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
|
||||
addModifiedPatchSet(
|
||||
changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
|
||||
|
||||
DiffInfo diffInfo =
|
||||
getDiffRequest(changeId, CURRENT, FILE_NAME)
|
||||
.withBase(previousPatchSetId)
|
||||
.withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
|
||||
.get();
|
||||
// We don't list the full file contents here as that is not the focus of this test.
|
||||
assertThat(diffInfo)
|
||||
.content()
|
||||
.element(0)
|
||||
.commonLines()
|
||||
.containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffOfUnmodifiedFileWithCommentAndWholeFileContextReturnsFileContents()
|
||||
throws Exception {
|
||||
addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
|
||||
String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
|
||||
CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
|
||||
ReviewInput reviewInput = new ReviewInput();
|
||||
reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
|
||||
gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
|
||||
addModifiedPatchSet(
|
||||
changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
|
||||
|
||||
DiffInfo diffInfo =
|
||||
getDiffRequest(changeId, CURRENT, FILE_NAME)
|
||||
.withBase(previousPatchSetId)
|
||||
.withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
|
||||
.get();
|
||||
// We don't list the full file contents here as that is not the focus of this test.
|
||||
assertThat(diffInfo)
|
||||
.content()
|
||||
.element(0)
|
||||
.commonLines()
|
||||
.containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffOfNonExistentFileIsAnEmptyDiffResult() throws Exception {
|
||||
addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
|
||||
|
||||
DiffInfo diffInfo =
|
||||
getDiffRequest(changeId, CURRENT, "a_non-existent_file.txt")
|
||||
.withBase(initialPatchSetId)
|
||||
.withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
|
||||
.get();
|
||||
assertThat(diffInfo).content().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult() throws Exception {
|
||||
addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
|
||||
String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
|
||||
String newFilePath = "a_new_file.txt";
|
||||
gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
|
||||
gApi.changes().id(changeId).edit().publish();
|
||||
|
||||
DiffInfo diffInfo =
|
||||
getDiffRequest(changeId, CURRENT, FILE_NAME)
|
||||
.withBase(previousPatchSetId)
|
||||
.withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
|
||||
.get();
|
||||
// This behavior has been present in Gerrit for quite some time. It differs from the results
|
||||
// returned for other cases (e.g. requesting the diff with whole file context for an unmodified
|
||||
// file; requesting the diff with whole file context for a non-existent file). However, it's not
|
||||
// completely clear what should be returned. The closest would be the result of a file deletion
|
||||
// but that might also be misleading for users as actually a file rename occurred. In fact,
|
||||
// requesting the diff result for the old file name of a renamed file is not a reasonable use
|
||||
// case at all. We at least guarantee that we don't run into an internal error.
|
||||
assertThat(diffInfo).content().element(0).commonLines().isNull();
|
||||
assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestingDiffForOldFileNameOfRenamedFileWithCommentOnOldFileYieldsReasonableResult()
|
||||
throws Exception {
|
||||
addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
|
||||
String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
|
||||
CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
|
||||
ReviewInput reviewInput = new ReviewInput();
|
||||
reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
|
||||
gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
|
||||
String newFilePath = "a_new_file.txt";
|
||||
gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
|
||||
gApi.changes().id(changeId).edit().publish();
|
||||
|
||||
DiffInfo diffInfo =
|
||||
getDiffRequest(changeId, CURRENT, FILE_NAME)
|
||||
.withBase(previousPatchSetId)
|
||||
.withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
|
||||
.get();
|
||||
// See comment for requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult().
|
||||
// This test should additionally ensure that we also don't run into an internal error when
|
||||
// a comment is present.
|
||||
assertThat(diffInfo).content().element(0).commonLines().isNull();
|
||||
assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
|
||||
}
|
||||
|
||||
private static CommentInput createCommentInput(
|
||||
int startLine, int startCharacter, int endLine, int endCharacter, String message) {
|
||||
CommentInput comment = new CommentInput();
|
||||
comment.range = new Comment.Range();
|
||||
comment.range.startLine = startLine;
|
||||
comment.range.startCharacter = startCharacter;
|
||||
comment.range.endLine = endLine;
|
||||
comment.range.endCharacter = endCharacter;
|
||||
comment.message = message;
|
||||
return comment;
|
||||
}
|
||||
|
||||
private void assertDiffForNewFile(
|
||||
PushOneCommit.Result pushResult, String path, String expectedContentSideB) throws Exception {
|
||||
DiffInfo diff =
|
||||
|
||||
Reference in New Issue
Block a user