Merge "Return ResourceConflict error when suggested fix can't be applied." into stable-3.11
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java b/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
index 4e05420..be1bced 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
@@ -56,6 +56,7 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.patch.PatchApplier;
+import org.eclipse.jgit.patch.PatchApplier.Result.Error;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -86,8 +87,13 @@
   @Override
   public Response<EditInfo> apply(
       RevisionResource revisionResource, ApplyProvidedFixInput applyProvidedFixInput)
-      throws AuthException, BadRequestException, ResourceConflictException, IOException,
-          ResourceNotFoundException, PermissionBackendException, RestApiException {
+      throws AuthException,
+          BadRequestException,
+          ResourceConflictException,
+          IOException,
+          ResourceNotFoundException,
+          PermissionBackendException,
+          RestApiException {
     if (applyProvidedFixInput == null) {
       throw new BadRequestException("applyProvidedFixInput is required");
     }
@@ -173,7 +179,8 @@
         String targetCommitMessage = targetCommitMessageFile.modifiableContent();
         if (!originCommitMessage.equals(targetCommitMessage)) {
           throw new ResourceConflictException(
-              "The fix attempts to modify commit message of an older patchset, but commit message has been updated in a newer patchset. The fix can't be applied.");
+              "The fix attempts to modify commit message of an older patchset, but commit message"
+                  + " has been updated in a newer patchset. The fix can't be applied.");
         }
         resultBuilder.newCommitMessage(originCommitModification.newCommitMessage().get());
       }
@@ -185,9 +192,20 @@
       try (ObjectInserter oi = repository.newObjectInserter()) {
         ApplyPatchInput inp = new ApplyPatchInput();
         inp.patch = patch;
-        inp.allowConflicts = false;
+        // Allow conflicts for showing more precise error message.
+        inp.allowConflicts = true;
         result =
             ApplyPatchUtil.applyPatch(repository, oi, inp, repository.parseCommit(targetCommit));
+        if (!result.getErrors().isEmpty()) {
+          String errorMessage =
+              (result.getErrors().stream().anyMatch(Error::isGitConflict)
+                      ? "Merge conflict while applying a fix:\n"
+                      : "Error while applying a fix:\n")
+                  + result.getErrors().stream()
+                      .map(Error::toString)
+                      .collect(Collectors.joining("\n"));
+          throw new ResourceConflictException(errorMessage);
+        }
         oi.flush();
         for (String path : result.getPaths()) {
           try (TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), path, result.getTreeId())) {
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java b/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
index d66a0a5..effd96f 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
@@ -378,8 +378,17 @@
         .value()
         .asString()
         .isEqualTo(
-            "New line at the start\nFirst line\nSecond line\nTModified contentrd line\nFourth line\nFifth line\n"
-                + "Sixth line\nSeventh line\nEighth line\nNinth line\nTenth line\n");
+            "New line at the start\n"
+                + "First line\n"
+                + "Second line\n"
+                + "TModified contentrd line\n"
+                + "Fourth line\n"
+                + "Fifth line\n"
+                + "Sixth line\n"
+                + "Seventh line\n"
+                + "Eighth line\n"
+                + "Ninth line\n"
+                + "Tenth line\n");
 
     applyProvidedFixInput = createApplyProvidedFixInput(FILE_NAME, "(1st)", 1, 5, 1, 5);
     applyProvidedFixInput.originalPatchsetForFix = previousRevision;
@@ -390,8 +399,17 @@
         .value()
         .asString()
         .isEqualTo(
-            "New line at the start\nFirst(1st) line\nSecond line\nTModified contentrd line\nFourth line\nFifth line\n"
-                + "Sixth line\nSeventh line\nEighth line\nNinth line\nTenth line\n");
+            "New line at the start\n"
+                + "First(1st) line\n"
+                + "Second line\n"
+                + "TModified contentrd line\n"
+                + "Fourth line\n"
+                + "Fifth line\n"
+                + "Sixth line\n"
+                + "Seventh line\n"
+                + "Eighth line\n"
+                + "Ninth line\n"
+                + "Tenth line\n");
   }
 
   @Test
@@ -440,8 +458,15 @@
         .value()
         .asString()
         .isEqualTo(
-            "Third line\nFourth line\nFifth line\n"
-                + "Sixth line\nSeventh line\nEighth line\nNinth line\nFirst modification\nTenth line\n");
+            "Third line\n"
+                + "Fourth line\n"
+                + "Fifth line\n"
+                + "Sixth line\n"
+                + "Seventh line\n"
+                + "Eighth line\n"
+                + "Ninth line\n"
+                + "First modification\n"
+                + "Tenth line\n");
     Optional<BinaryResult> file2 = gApi.changes().id(changeId).edit().getFile(FILE_NAME2);
     BinaryResultSubject.assertThat(file2)
         .value()
@@ -450,6 +475,44 @@
   }
 
   @Test
+  public void applyProvidedFixOnNewerPatchset_mergeConflictThrowsResourceConflictException()
+      throws Exception {
+    // Remember patch set and add another one.
+    int previousRevision = gApi.changes().id(changeId).get().currentRevisionNumber;
+    // Change first 2 lines;
+    String modifiedContent =
+        "abc\ndef\n"
+            + FILE_CONTENT.substring(
+                FILE_CONTENT.indexOf("\n", FILE_CONTENT.indexOf("\n") + 1) + 1);
+    amendChange(
+        changeId,
+        "refs/for/master",
+        admin,
+        testRepo,
+        PushOneCommit.SUBJECT,
+        FILE_NAME,
+        modifiedContent);
+    FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
+    fixReplacementInfo1.path = FILE_NAME;
+    fixReplacementInfo1.range = createRange(2, 0, 2, 0);
+    fixReplacementInfo1.replacement = "First modification\n";
+
+    ApplyProvidedFixInput applyProvidedFixInput = new ApplyProvidedFixInput();
+
+    applyProvidedFixInput.fixReplacementInfos = Arrays.asList(fixReplacementInfo1);
+    applyProvidedFixInput.originalPatchsetForFix = previousRevision;
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.changes().id(changeId).current().applyFix(applyProvidedFixInput));
+
+    assertThat(thrown.getMessage()).contains("Merge conflict");
+    // Change edit must not be created
+    assertThat(gApi.changes().id(changeId).edit().get()).isEmpty();
+  }
+
+  @Test
   public void applyProvidedFixOnCommitMessageCanBeAppliedToNewerPatchset() throws Exception {
     // Set a dedicated commit message.
     String footer = "\nChange-Id: " + changeId + "\n";