Fix "Conflicts With" when current change is a merge commit

The "Conflicts With" for a change which itself is a merge commit wasn't
computed properly because the list of affected files by the merge-commit
is either empty or contains only those files where conflicts where
resolved. Because of that, only few other changes had an overlap over
the affected files and only those changes where considered as the
candidates for the "Conflicts With" list.

To make this work we need to compute the list of affected files
properly. Taking an example of an open change which is a merge:

      master > o
               |
               o  o Change-123
               | /|
  merge-base > o  o (changes a.java)
               | /
               o

to compute the set of affected files by the Change-123 relative to its
target branch (master) we take the list of files which is different
between the merge-base and the Change-123. In this example the
file a.java will be included in the list of affected files.

Bug: issue 3052
Change-Id: Ic20ea9c348f78a2dfca2448346a7719b7937eef2
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 95d0422..e356f64 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -42,8 +42,12 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -64,8 +68,7 @@
     for (final Change c : changes) {
       final ChangeDataCache changeDataCache = new ChangeDataCache(
           c, db, args.changeDataFactory, args.projectCache);
-      List<String> files = args.changeDataFactory.create(db.get(), c)
-          .currentFilePaths();
+      List<String> files = listFiles(c, args, changeDataCache);
       List<Predicate<ChangeData>> filePredicates =
           Lists.newArrayListWithCapacity(files.size());
       for (String file : files) {
@@ -196,6 +199,42 @@
     return changePredicates;
   }
 
+  private static List<String> listFiles(Change c, Arguments args,
+      ChangeDataCache changeDataCache) throws OrmException {
+    try (Repository repo = args.repoManager.openRepository(c.getProject());
+        RevWalk rw = new RevWalk(repo)) {
+      RevCommit ps = rw.parseCommit(changeDataCache.getTestAgainst());
+      if (ps.getParentCount() > 1) {
+        String dest = c.getDest().get();
+        Ref destBranch = repo.getRefDatabase().getRef(dest);
+        destBranch.getObjectId();
+        rw.setRevFilter(RevFilter.MERGE_BASE);
+        rw.markStart(rw.parseCommit(destBranch.getObjectId()));
+        rw.markStart(ps);
+        RevCommit base = rw.next();
+        // TODO(zivkov): handle the case with multiple merge bases
+
+        List<String> files = new ArrayList<>();
+        try (TreeWalk tw = new TreeWalk(repo)) {
+          if (base != null) {
+            tw.setFilter(TreeFilter.ANY_DIFF);
+            tw.addTree(base.getTree());
+          }
+          tw.addTree(ps.getTree());
+          tw.setRecursive(true);
+          while (tw.next()) {
+            files.add(tw.getPathString());
+          }
+        }
+        return files;
+      } else {
+        return args.changeDataFactory.create(args.db.get(), c).currentFilePaths();
+      }
+    } catch (IOException e) {
+      throw new OrmException(e);
+    }
+  }
+
   @Override
   public String toString() {
     return ChangeQueryBuilder.FIELD_CONFLICTS + ":" + value;