Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  Bazel: Include eclipse-out directory in .bazelignore

Change-Id: I3d3d6bbb187527d4579e9d514fac35fc6c44df61
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/WORKSPACE b/WORKSPACE
index e98960f..809778d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "714a32382ebd02919007d3514513af4395768d80",
+    commit = "f1d3eefb78029298afe119b0d8b2a43de2b510f6",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
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 db37a37..ee8801f 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/AtomicityHelper.java
@@ -1,24 +1,28 @@
 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.NoSuchProjectException;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
@@ -34,12 +38,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 +50,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.
    *
@@ -64,10 +68,13 @@
    * @return true or false
    * @throws IOException
    * @throws NoSuchChangeException
+   * @throws NoSuchProjectException
    * @throws OrmException
+   * @throws PermissionBackendException
    */
   public boolean hasDependentReview(String project, int number)
-      throws IOException, NoSuchChangeException, OrmException {
+      throws IOException, NoSuchChangeException, NoSuchProjectException, OrmException,
+          PermissionBackendException {
     RevisionResource r = getRevisionResource(project, number);
     RelatedInfo related = getRelated.apply(r);
     log.debug(String.format("Checking for related changes on review %d", number));
@@ -105,7 +112,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 +138,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(getBotUser(), 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,40 +153,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 {
-    submitter.apply(getRevisionResource(project, changeNumber), new SubmitInput());
+  /** 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 cc5347b..8145d46 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;
@@ -29,20 +30,15 @@
 import com.google.gerrit.server.data.ChangeAttribute;
 import com.google.gerrit.server.events.ChangeEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.DraftPublishedEvent;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 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;
@@ -76,7 +72,6 @@
   @Override
   public synchronized void onEvent(final Event event) {
     if (event instanceof TopicChangedEvent
-        || event instanceof DraftPublishedEvent
         || event instanceof ReviewerDeletedEvent
         || // A blocking score might be removed when a reviewer is deleted.
         event instanceof PatchSetCreatedEvent) {
@@ -98,7 +93,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 +107,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 +132,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 +149,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);
@@ -183,14 +177,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(
@@ -224,8 +223,7 @@
     }
   }
 
-  private void attemptToMergeNonAtomic(Change change)
-      throws IOException, OrmException, RestApiException {
+  private void attemptToMergeNonAtomic(Change change) throws Exception {
     // There may be a parent commit that it not merged while having all approvals
     // because it is part of a cross-repo. We take care to not let Gerrit merge it
     // by merging only the commits whose parents are already merged.
@@ -255,7 +253,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);
   }
diff --git a/test.rb b/test.rb
index 995cbb9..859d375 100755
--- a/test.rb
+++ b/test.rb
@@ -179,16 +179,6 @@
     check_status(commit2, 'MERGED')
   end
 
-  def test_published_draft_is_autosubmitted
-    commit_id = create_review(PROJECT1, "review0 on #{PROJECT1}", draft: true)
-    approve_review(commit_id)
-    check_status(commit_id, 'DRAFT')
-
-    publish_draft(commit_id)
-
-    check_status(commit_id, 'MERGED')
-  end
-
   private
 
   def project_dir(project_name)
@@ -205,11 +195,7 @@
     reviews = gerrit_query(query)
     reviews.each do |review|
       review_number = review['number']
-      if review['status'] == 'DRAFT'
-        execute("#{GERRIT_SSH} gerrit review --delete #{review_number},1")
-      else
-        execute("#{GERRIT_SSH} gerrit review --abandon #{review_number},1")
-      end
+      execute("#{GERRIT_SSH} gerrit review --abandon #{review_number},1")
     end
   end
 
@@ -221,14 +207,14 @@
     change_id
   end
 
-  def create_review(project_name, message, topic = nil, change_id = nil, draft: false)
+  def create_review(project_name, message, topic = nil, change_id = nil)
     topic_suffix = "/#{topic}" if topic
     message = "#{message}\n\nChange-Id: #{change_id}" if change_id
     execute(["cd #{project_dir(project_name)}",
              "echo 0 >> a",
              "git add .",
              %Q(git commit -m "#{message}"),
-             "git push origin HEAD:refs/#{draft ? 'drafts' : 'for'}/master#{topic_suffix}"
+             "git push origin HEAD:refs/for/master#{topic_suffix}"
             ].join(" && "))
     commit_id = execute("cd #{project_dir(project_name)} && git rev-parse HEAD")
     refute(commit_id.empty?, "missing commit-id")
@@ -236,11 +222,7 @@
   end
 
   def approve_review(commit_id)
-    execute("#{GERRIT_SSH} gerrit review --verified 1 --code-review 2 #{commit_id}")
-  end
-
-  def publish_draft(commit_id)
-    execute("#{GERRIT_SSH} gerrit review --publish #{commit_id}")
+    execute("#{GERRIT_SSH} gerrit review --strict-labels --verified 1 --code-review 2 #{commit_id}")
   end
 
   def abandon_review(commit_id)
@@ -251,15 +233,17 @@
     reviews = gerrit_query("commit:#{commit_id}")
     assert_equal(1, reviews.size, "missing review with commit #{commit_id}")
     review = reviews[0]
-    assert_equal(expected_status, review['status'], "wrong status on review: #{review['number']}")
+    assert_equal(expected_status, review['status'], "wrong status on review #{review['number']} '#{review['subject']}'")
   end
 
   def check_label(commit_id, label_name, expected_label_value)
     reviews = gerrit_query("commit:#{commit_id}", "--all-approvals")
     assert_equal(1, reviews.size, "missing review with commit #{commit_id}")
     review = reviews[0]
-    code_review_approvals = review['patchSets'][0]['approvals'].select {|ap| ap['description'] == "Code-Review"}
-    refute(code_review_approvals.empty?)
+    approvals = review['patchSets'][0]['approvals']
+    refute(approvals.nil?, "No approval on #{commit_id}")
+    code_review_approvals = approvals.select {|ap| ap['description'] == "Code-Review"}
+    refute(code_review_approvals.empty?, "No code-review score on #{commit_id}")
     assert_equal(expected_label_value, code_review_approvals[0]['value'], "wrong label on review: #{review['number']}")
   end