Merge branch 'stable-2.14' into HEAD

The test 'test_published_draft_is_autosubmitted' is failing because
the drafts are now 'private' and merged. There is no more need to
'publish' them.
The test should be replaced by a test on the 'wip'
new feature but currently no event is propagated to the EventListener
when pushing '%ready'.

Change-Id: I9622ba642d736ecffd480584525a1f142bb8c14c
diff --git a/README.md b/README.md
index a96c3c5..372bba1 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 Autosubmitter
 =============
 
-[![Build Status](https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.14/job/plugin-automerge-plugin-gh-bazel-stable-2.14/badge/icon)](https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.14/job/plugin-automerge-plugin-gh-bazel-stable-2.14/)
+[![Build Status](https://gerrit-ci.gerritforge.com/job/plugin-autosubmitter-bazel-master/badge/icon)](https://gerrit-ci.gerritforge.com/job/plugin-autosubmitter-bazel-master/)
 
 A [gerrit](https://www.gerritcodereview.com) plugin that takes care of
 automatically merging reviews when all approvals are present.
diff --git a/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java b/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java
index 18471b3..98d67b3 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java
@@ -1,23 +1,26 @@
 package com.criteo.gerrit.plugins.automerge;
 
+import static com.google.gerrit.server.permissions.ChangePermission.READ;
+
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.account.Emails;
 import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.GetRelated;
 import com.google.gerrit.server.change.GetRelated.ChangeAndCommit;
 import com.google.gerrit.server.change.GetRelated.RelatedInfo;
-import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -34,12 +37,8 @@
 
   private static final Logger log = LoggerFactory.getLogger(AtomicityHelper.class);
 
-  @Inject private AccountByEmailCache byEmailCache;
-
   @Inject ChangeData.Factory changeDataFactory;
 
-  @Inject private ChangeControl.GenericFactory changeFactory;
-
   @Inject private ChangesCollection collection;
 
   @Inject AutomergeConfig config;
@@ -50,12 +49,16 @@
 
   @Inject GetRelated getRelated;
 
-  @Inject MergeUtil.Factory mergeUtilFactory;
-
-  @Inject Provider<PostReview> reviewer;
-
   @Inject Submit submitter;
 
+  @Inject Emails emails;
+
+  @Inject ChangeNotes.Factory changeNotesFactory;
+
+  @Inject PermissionBackend permissionBackend;
+
+  @Inject SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
+
   /**
    * Check if the current patchset of the specified change has dependent unmerged changes.
    *
@@ -105,7 +108,7 @@
    * Check if a change is an atomic change or not. A change is atomic if it has the atomic topic
    * prefix.
    *
-   * @param change a ChangeAttribute instance
+   * @param change a Change instance
    * @return true or false
    */
   public boolean isAtomicReview(final Change change) {
@@ -131,7 +134,10 @@
             new com.google.gerrit.reviewdb.client.Change.Id(change));
     // For draft reviews, the patchSet must be set to avoid an NPE.
     final List<SubmitRecord> cansubmit =
-        new SubmitRuleEvaluator(changeData).setPatchSet(changeData.currentPatchSet()).evaluate();
+        submitRuleEvaluatorFactory
+            .create(changeData)
+            .setPatchSet(changeData.currentPatchSet())
+            .evaluate();
     log.debug(String.format("Checking if change %d is submitable.", change));
     for (SubmitRecord submit : cansubmit) {
       if (submit.status != SubmitRecord.Status.OK) {
@@ -143,43 +149,37 @@
     return true;
   }
 
-  /**
-   * Merge a review.
-   *
-   * @param info
-   * @throws RestApiException
-   * @throws NoSuchChangeException
-   * @throws OrmException
-   * @throws IOException
-   */
-  public void mergeReview(String project, int changeNumber)
-      throws RestApiException, NoSuchChangeException, OrmException, IOException {
-    final SubmitInput input = new SubmitInput();
-    input.waitForMerge = true;
-    final RevisionResource r = getRevisionResource(project, changeNumber);
-    submitter.apply(r, input);
+  /** Merge a review. */
+  public void mergeReview(String project, int number) throws Exception {
+    submitter.apply(getRevisionResource(project, number), new SubmitInput());
   }
 
   public RevisionResource getRevisionResource(String project, int changeNumber)
-      throws NoSuchChangeException, OrmException {
-    ChangeControl ctl =
-        changeFactory.validateFor(
-            db.get(), new com.google.gerrit.reviewdb.client.Change.Id(changeNumber), getBotUser());
-    ChangeData changeData =
-        changeDataFactory.create(
-            db.get(),
-            new Project.NameKey(project),
-            new com.google.gerrit.reviewdb.client.Change.Id(changeNumber));
-    RevisionResource r = new RevisionResource(collection.parse(ctl), changeData.currentPatchSet());
-    return r;
+      throws OrmException {
+    com.google.gerrit.reviewdb.client.Change.Id changeId =
+        new com.google.gerrit.reviewdb.client.Change.Id(changeNumber);
+    ChangeNotes notes = changeNotesFactory.createChecked(changeId);
+    try {
+      permissionBackend.user(getBotUser()).change(notes).database(db).check(READ);
+      ChangeData changeData =
+          changeDataFactory.create(db.get(), new Project.NameKey(project), changeId);
+      RevisionResource r =
+          new RevisionResource(collection.parse(changeId), changeData.currentPatchSet());
+      return r;
+    } catch (ResourceNotFoundException | AuthException | PermissionBackendException e) {
+      throw new NoSuchChangeException(changeId);
+    }
   }
 
   private IdentifiedUser getBotUser() {
-    final Set<Account.Id> ids = byEmailCache.get(config.getBotEmail());
-    if (ids.isEmpty()) {
-      throw new RuntimeException("No user found with email: " + config.getBotEmail());
+    try {
+      Set<Account.Id> ids = emails.getAccountFor(config.getBotEmail());
+      if (ids.isEmpty()) {
+        throw new RuntimeException("No user found with email: " + config.getBotEmail());
+      }
+      return factory.create(ids.iterator().next());
+    } catch (IOException | OrmException e) {
+      throw new RuntimeException("Unable to get account with email: " + config.getBotEmail(), e);
     }
-    final IdentifiedUser bot = factory.create(ids.iterator().next());
-    return bot;
   }
 }
diff --git a/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java b/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java
index 4696b2d..864de7d 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.events.LifecycleListener;
@@ -36,13 +37,9 @@
 import com.google.gerrit.server.events.ReviewerDeletedEvent;
 import com.google.gerrit.server.events.TopicChangedEvent;
 import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.update.UpdateException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import java.io.IOException;
 import java.util.EnumSet;
 import java.util.List;
 import org.slf4j.Logger;
@@ -98,7 +95,7 @@
     }
     try {
       autoSubmitIfMergeable(change);
-    } catch (OrmException | RestApiException | IOException | UpdateException e) {
+    } catch (Exception e) {
       log.error("An exception occured while trying to merge change #" + change.number, e);
     }
   }
@@ -112,7 +109,7 @@
     try {
       checkReviewExists(change.number);
       autoSubmitIfMergeable(Change.from(change));
-    } catch (RestApiException | OrmException | UpdateException | IOException e) {
+    } catch (Exception e) {
       log.error("An exception occured while trying to atomic merge a change.", e);
       throw new RuntimeException(e);
     }
@@ -137,7 +134,7 @@
                           + refName
                           + ": Submitting ...");
                   autoSubmitIfMergeable(Change.from(submittable));
-                } catch (UpdateException | RestApiException | OrmException | IOException e) {
+                } catch (Exception e) {
                   log.error(
                       "Cannot autosubmit change "
                           + submittable._number
@@ -154,8 +151,7 @@
     }
   }
 
-  private void autoSubmitIfMergeable(Change change)
-      throws OrmException, RestApiException, NoSuchChangeException, IOException, UpdateException {
+  private void autoSubmitIfMergeable(Change change) throws Exception {
     if (atomicityHelper.isSubmittable(change.project, change.number)) {
       if (atomicityHelper.isAtomicReview(change)) {
         attemptToMergeAtomic(change);
@@ -184,14 +180,19 @@
     return false;
   }
 
-  private void attemptToMergeAtomic(Change change)
-      throws RestApiException, OrmException, NoSuchChangeException, IOException, UpdateException {
+  private void attemptToMergeAtomic(Change change) throws Exception {
     final List<ChangeInfo> related = Lists.newArrayList();
-    related.addAll(
-        api.changes()
-            .query("status: open AND topic: " + change.topic)
-            .withOption(ListChangesOption.CURRENT_REVISION)
-            .get());
+    if (atomicityHelper.isAtomicReview(change)) {
+      related.addAll(
+          api.changes()
+              .query("status: open AND topic: " + change.topic)
+              .withOption(ListChangesOption.CURRENT_REVISION)
+              .get());
+    } else {
+      ChangeApi changeApi = api.changes().id(change.project, change.branch, change.id);
+      related.add(changeApi.get(EnumSet.of(ListChangesOption.CURRENT_REVISION)));
+    }
+
     for (final ChangeInfo info : related) {
       if (!atomicityHelper.isSubmittable(info.project, info._number)) {
         log.info(
@@ -239,7 +240,7 @@
         reviewUpdater.commentOnReview(
             change.project, change.number, config.atomicReviewsSameRepo.getContent());
       }
-    } catch (RestApiException | IOException | OrmException | UpdateException e) {
+    } catch (Exception e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/main/java/com/criteo/gerrit/plugins/automerge/Change.java b/src/main/java/com/criteo/gerrit/plugins/automerge/Change.java
index 3216cca..8728bed 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/Change.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/Change.java
@@ -12,19 +12,29 @@
 public class Change {
   public final String project;
   public final int number;
+  public final String id;
   public final String topic;
+  public final String branch;
 
-  private Change(String project, int number, String topic) {
+  private Change(String project, int number, String id, String topic, String branch) {
     this.project = project;
     this.number = number;
+    this.id = id;
     this.topic = topic;
+    this.branch = branch;
   }
 
   public static Change from(ChangeAttribute changeAttribute) {
-    return new Change(changeAttribute.project, changeAttribute.number, changeAttribute.topic);
+    return new Change(
+        changeAttribute.project,
+        changeAttribute.number,
+        changeAttribute.id,
+        changeAttribute.topic,
+        changeAttribute.branch);
   }
 
   public static Change from(ChangeInfo changeInfo) {
-    return new Change(changeInfo.project, changeInfo._number, changeInfo.topic);
+    return new Change(
+        changeInfo.project, changeInfo._number, changeInfo.id, changeInfo.topic, changeInfo.branch);
   }
 }
diff --git a/src/main/java/com/criteo/gerrit/plugins/automerge/ReviewUpdater.java b/src/main/java/com/criteo/gerrit/plugins/automerge/ReviewUpdater.java
index d5f982f..690ebc0 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/ReviewUpdater.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/ReviewUpdater.java
@@ -1,14 +1,10 @@
 package com.criteo.gerrit.plugins.automerge;
 
 import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.update.UpdateException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import java.io.IOException;
 
 public class ReviewUpdater {
   /**
@@ -22,8 +18,7 @@
 
   @Inject private AtomicityHelper atomicityHelper;
 
-  public void commentOnReview(String project, int number, String comment)
-      throws RestApiException, OrmException, IOException, UpdateException {
+  public void commentOnReview(String project, int number, String comment) throws Exception {
     ReviewInput reviewInput = createComment(comment);
     applyComment(project, number, reviewInput);
   }
@@ -32,8 +27,7 @@
     return new ReviewInput().message(commentsPrefix + comment);
   }
 
-  private void applyComment(String project, int number, ReviewInput comment)
-      throws RestApiException, OrmException, IOException, UpdateException {
+  private void applyComment(String project, int number, ReviewInput comment) throws Exception {
     RevisionResource r = atomicityHelper.getRevisionResource(project, number);
     reviewer.get().apply(r, comment);
   }