Add AutomaticMerger logic

Automatically merge mergeable reviews that are on the same atomic topic
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 70c17bc..7857f49 100644
--- a/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java
+++ b/src/main/java/com/criteo/gerrit/plugins/automerge/AutomaticMerger.java
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//   http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,26 +14,221 @@
 
 package com.criteo.gerrit.plugins.automerge;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+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.change.ChangesCollection;
+import com.google.gerrit.server.change.GetRelated;
+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.config.GerritServerConfig;
+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.PatchSetCreatedEvent;
+import com.google.gerrit.server.events.TopicChangedEvent;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Config;
 
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
 
 /**
- * Starts at the same time as the gerrit server, and sets up our
- * change hook listener.
+ * Starts at the same time as the gerrit server, and sets up our change hook
+ * listener.
  */
 public class AutomaticMerger implements ChangeListener, LifecycleListener {
 
+  @Inject
+  private GerritApi api;
+  private final AtomicityHelper atomicityHelper;
+
+  @Inject
+  private AccountByEmailCache byEmailCache;
+
+  @Inject
+  ChangeData.Factory changeDataFactory;
+
+  @Inject
+  private ChangeControl.GenericFactory changeFactory;
+
+  @Inject
+  private ChangesCollection collection;
+
   private final AutomergeConfig config;
 
   @Inject
-  public AutomaticMerger(@GerritServerConfig Config gerritConfig){
-    this.config = new AutomergeConfig(gerritConfig);
+  Provider<ReviewDb> db;
+
+  @Inject
+  private IdentifiedUser.GenericFactory factory;
+
+  @Inject
+  GetRelated getRelated;
+
+  @Inject
+  MergeUtil.Factory mergeUtilFactory;
+
+  @Inject
+  Provider<PostReview> reviewer;
+  private final ReviewUpdater reviewUpdater;
+  @Inject
+  Submit submitter;
+
+  @Inject
+  public AutomaticMerger(@GerritServerConfig final Config gerritConfig) {
+    config = new AutomergeConfig(gerritConfig);
+    reviewUpdater = new ReviewUpdater(config);
+    atomicityHelper = new AtomicityHelper(config);
+  }
+
+  @Override
+  synchronized public void onChangeEvent(final ChangeEvent event) {
+    try {
+      if (event instanceof TopicChangedEvent) {
+        final TopicChangedEvent newComment = (TopicChangedEvent) event;
+        final ChangeAttribute change = newComment.change;
+        final int reviewNumber = Integer.parseInt(change.number);
+        try {
+          api.changes().id(reviewNumber).get(EnumSet.of(ListChangesOption.CURRENT_REVISION));
+        } catch (final RestApiException e1) {
+          throw new RuntimeException(e1);
+        }
+
+        if (atomicityHelper.isAtomicReview(change)) {
+          if (atomicityHelper.hasDependentReview(reviewNumber)) {
+            reviewUpdater.setMinusTwo(reviewNumber,
+                config.getCommentFromFile(AutomergeConfig.ATOMIC_REVIEWS_SAME_REPO_FILE));
+          } else {
+            reviewUpdater.commentOnReview(reviewNumber,
+                config.getCommentFromFile(AutomergeConfig.ATOMIC_REVIEW_DETECTED_FILE));
+          }
+        }
+      }
+      if (event instanceof PatchSetCreatedEvent) {
+        final PatchSetCreatedEvent newComment = (PatchSetCreatedEvent) event;
+
+        final ChangeAttribute change = newComment.change;
+        final int reviewNumber = Integer.parseInt(change.number);
+        try {
+          api.changes().id(reviewNumber).get(EnumSet.of(ListChangesOption.CURRENT_REVISION));
+        } catch (final RestApiException e1) {
+          throw new RuntimeException(e1);
+        }
+        if (atomicityHelper.isAtomicReview(change)) {
+          if (atomicityHelper.hasDependentReview(reviewNumber)) {
+            reviewUpdater.setMinusTwo(reviewNumber,
+                config.getCommentFromFile(AutomergeConfig.ATOMIC_REVIEWS_SAME_REPO_FILE));
+          } else {
+            reviewUpdater.commentOnReview(reviewNumber,
+                config.getCommentFromFile(AutomergeConfig.ATOMIC_REVIEW_DETECTED_FILE));
+          }
+        }
+      }
+
+
+    } catch (NoSuchChangeException | OrmException | IOException | AuthException | BadRequestException
+        | UnprocessableEntityException e) {
+      throw new RuntimeException(e);
+    }
+    if (event instanceof CommentAddedEvent) {
+      final CommentAddedEvent newComment = (CommentAddedEvent) event;
+      final ChangeAttribute change = newComment.change;
+      final int reviewNumber = Integer.parseInt(change.number);
+      try {
+        api.changes().id(reviewNumber).get(EnumSet.of(ListChangesOption.CURRENT_REVISION));
+      } catch (final RestApiException e1) {
+        throw new RuntimeException(e1);
+      }
+
+      if (newComment.author.email.contains("qabot")) {
+        return;
+      }
+      final String topic = change.topic;
+      try {
+        if (atomicityHelper.isSubmittable(Integer.parseInt(newComment.change.number))) {
+          final List<ChangeInfo> related = Lists.newArrayList();
+          if (topic != null) {
+            related.addAll(api.changes().query("status: open AND topic: " + topic)
+                .withOption(ListChangesOption.CURRENT_REVISION).get());
+          } else {
+            related.add(api.changes().id(change.id).get(EnumSet.of(ListChangesOption.CURRENT_REVISION)));
+          }
+          boolean mergeable = true;
+          String why = null;
+          for (final ChangeInfo info : related) {
+            api.changes().id(change.id).get(EnumSet.of(ListChangesOption.CURRENT_REVISION));
+            if (!info.mergeable) {
+              mergeable = false;
+              why = String.format("Review %s is approved but not mergeable.", info._number);
+            }
+            if (!atomicityHelper.isSubmittable(info._number)) {
+              mergeable = false;
+              // why = String.format("Review %s is not approved.",
+              // info._number);
+            }
+          }
+          if (mergeable) {
+            for (final ChangeInfo info : related) {
+              final SubmitInput input = new SubmitInput();
+              input.waitForMerge = true;
+              final Set<Account.Id> ids = byEmailCache.get(config.getBotEmail());
+              final IdentifiedUser bot = factory.create(ids.iterator().next());
+              final ChangeControl ctl = changeFactory.controlFor(new Change.Id(info._number), bot);
+              final ChangeData changeData = changeDataFactory.create(db.get(), new Change.Id(info._number));
+
+              final RevisionResource r = new RevisionResource(collection.parse(ctl), changeData.currentPatchSet());
+              submitter.apply(r, input);
+            }
+          } else {
+            if (why != null) {
+              reviewUpdater.commentOnReview(reviewNumber,
+                  config.getCommentFromFile(AutomergeConfig.CANT_MERGE_COMMENT_FILE));
+            }
+          }
+        }
+      } catch (final RestApiException e) {
+        throw new RuntimeException(e);
+      } catch (final OrmException e) {
+        throw new RuntimeException(e);
+      } catch (final NumberFormatException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      } catch (final NoSuchChangeException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      } catch (final RepositoryNotFoundException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      } catch (final IOException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
   }
 
   @Override
@@ -43,9 +238,4 @@
   @Override
   public void stop() {
   }
-
-  @Override
-  synchronized public void onChangeEvent(ChangeEvent event) {
-
-  }
 }