Add diff3 formatter for merge conflicts.

Add the ability to show the merge conflicts in the diff3 format.
This feature is controlled by core.diff3ConflictView in the
gerrit.config.

Bug: 293650032
Change-Id: I6a2346a2588f0ca595e1af4ad444cad17becdf7a
Release-Notes: Add diff3 formatter for merge conflicts.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 2e175e6..32c05b0 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1672,6 +1672,16 @@
 +
 Default is 5 minutes.
 
+[[change.diff3ConflictView]]change.diff3ConflictView::
++
+Use the diff3 formatter for merge commits with conflicts. With diff3 when
+the conflicts are shown in the "Auto Merge" view, the base section from the
+common parents will be shown as well.
+This setting takes effect when generating the automerge, which happens on upload.
+Changing the setting leaves existing changes unaffected.
++
+Default is false.
+
 [[changeCleanup]]
 === Section changeCleanup
 
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index a522a2f..73aec64 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -286,7 +286,6 @@
     return commit;
   }
 
-  @SuppressWarnings("resource") // TemporaryBuffer requires calling close before reading.
   public static ObjectId mergeWithConflicts(
       RevWalk rw,
       ObjectInserter ins,
@@ -297,6 +296,21 @@
       RevCommit theirs,
       Map<String, MergeResult<? extends Sequence>> mergeResults)
       throws IOException {
+    return mergeWithConflicts(rw, ins, dc, oursName, ours, theirsName, theirs, mergeResults, false);
+  }
+
+  @SuppressWarnings("resource") // TemporaryBuffer requires calling close before reading.
+  public static ObjectId mergeWithConflicts(
+      RevWalk rw,
+      ObjectInserter ins,
+      DirCache dc,
+      String oursName,
+      RevCommit ours,
+      String theirsName,
+      RevCommit theirs,
+      Map<String, MergeResult<? extends Sequence>> mergeResults,
+      boolean diff3Format)
+      throws IOException {
     rw.parseBody(ours);
     rw.parseBody(theirs);
     String oursMsg = ours.getShortMessage();
@@ -324,7 +338,11 @@
       try {
         // TODO(dborowitz): Respect inCoreLimit here.
         buf = new TemporaryBuffer.LocalFile(null, 10 * 1024 * 1024);
-        fmt.formatMerge(buf, p, "BASE", oursNameFormatted, theirsNameFormatted, UTF_8);
+        if (diff3Format) {
+          fmt.formatMergeDiff3(buf, p, "BASE", oursNameFormatted, theirsNameFormatted, UTF_8);
+        } else {
+          fmt.formatMerge(buf, p, "BASE", oursNameFormatted, theirsNameFormatted, UTF_8);
+        }
         buf.close(); // Flush file and close for writes, but leave available for reading.
 
         try (InputStream in = buf.openInputStream()) {
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index 0818f23..e27faf6 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -83,6 +83,10 @@
     return cfg.getBoolean("change", null, "cacheAutomerge", true);
   }
 
+  public static boolean diff3ConflictView(Config cfg) {
+    return cfg.getBoolean("change", null, "diff3ConflictView", false);
+  }
+
   private enum OperationType {
     CACHE_LOAD,
     IN_MEMORY_WRITE,
@@ -93,6 +97,7 @@
   private final Timer1<OperationType> latency;
   private final Provider<PersonIdent> gerritIdentProvider;
   private final boolean save;
+  private final boolean useDiff3;
   private final ThreeWayMergeStrategy configuredMergeStrategy;
 
   @Inject
@@ -117,6 +122,7 @@
                 .setUnit("milliseconds"),
             operationTypeField);
     this.save = cacheAutomerge(cfg);
+    this.useDiff3 = diff3ConflictView(cfg);
     this.gerritIdentProvider = gerritIdentProvider;
     this.configuredMergeStrategy = MergeUtil.getMergeStrategy(cfg);
   }
@@ -254,7 +260,8 @@
               merge.getParent(0),
               "BRANCH",
               merge.getParent(1),
-              m.getMergeResults());
+              m.getMergeResults(),
+              useDiff3);
     }
     logger.atFine().log("AutoMerge treeId=%s", treeId.name());