Merge branch 'stable-2.11'

* stable-2.11:
  Revert "Replicate HEAD reference when replicating a project"

Change-Id: I1beaa53c388c5f0d832dec5bb25b1c880cca6237
diff --git a/BUCK b/BUCK
index 2e8a623..3da69ef 100644
--- a/BUCK
+++ b/BUCK
@@ -1,3 +1,5 @@
+include_defs('//lib/maven.defs')
+
 gerrit_plugin(
   name = 'replication',
   srcs = glob(['src/main/java/**/*.java']),
@@ -9,15 +11,27 @@
     'Gerrit-Module: com.googlesource.gerrit.plugins.replication.ReplicationModule',
     'Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule'
   ],
+  deps = [
+    ':commons-io',
+  ],
   provided_deps = [
-    '//lib/commons:io',
+    '//lib:gson',
     '//lib/log:log4j'
   ],
 )
 
+maven_jar(
+  name = 'commons-io',
+  id = 'commons-io:commons-io:1.4',
+  sha1 = 'a8762d07e76cfde2395257a5da47ba7c1dbd3dce',
+  license = 'Apache2.0',
+)
+
 java_test(
   name = 'replication_tests',
   srcs = glob(['src/test/java/**/*.java']),
+  labels = ['replication'],
+  source_under_test = [':replication__plugin'],
   deps = [
     ':replication__plugin',
     '//gerrit-common:server',
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
index 62cad2c..2c946a5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.replication;
 
+import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.server.PluginUser;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.config.SitePaths;
@@ -43,12 +44,14 @@
   private final GitRepositoryManager gitRepositoryManager;
   private final GroupBackend groupBackend;
   private final WorkQueue workQueue;
+  private final ReplicationStateListener stateLog;
 
   @Inject
   public AutoReloadConfigDecorator(Injector injector, SitePaths site,
       RemoteSiteUser.Factory ruf, PluginUser pu,
       GitRepositoryManager grm, GroupBackend gb,
-      WorkQueue workQueue) throws ConfigInvalidException,
+      WorkQueue workQueue,
+      ReplicationStateListener stateLog) throws ConfigInvalidException,
       IOException {
     this.injector = injector;
     this.site = site;
@@ -57,15 +60,20 @@
     this.gitRepositoryManager = grm;
     this.groupBackend = gb;
     this.currentConfig = loadConfig();
-    this.currentConfigTs = currentConfig.getCfgPath().lastModified();
+    this.currentConfigTs = getLastModified(currentConfig);
     this.workQueue = workQueue;
+    this.stateLog = stateLog;
+  }
+
+  private static long getLastModified(ReplicationFileBasedConfig cfg) {
+    return FileUtil.lastModified(cfg.getCfgPath());
   }
 
   private ReplicationFileBasedConfig loadConfig()
       throws ConfigInvalidException, IOException {
     return new ReplicationFileBasedConfig(injector, site,
         remoteSiteUserFactory, pluginUser, gitRepositoryManager,
-        groupBackend);
+        groupBackend, stateLog);
   }
 
   private synchronized boolean isAutoReload() {
@@ -79,25 +87,27 @@
   }
 
   private void reloadIfNeeded() {
-    if (isAutoReload()
-        && currentConfig.getCfgPath().lastModified() > currentConfigTs) {
-      try {
-        ReplicationFileBasedConfig newConfig = loadConfig();
-        newConfig.startup(workQueue);
-        int discarded = currentConfig.shutdown();
+    try {
+      if (isAutoReload()) {
+        long lastModified = getLastModified(currentConfig);
+        if (lastModified > currentConfigTs) {
+          ReplicationFileBasedConfig newConfig = loadConfig();
+          newConfig.startup(workQueue);
+          int discarded = currentConfig.shutdown();
 
-        this.currentConfig = newConfig;
-        this.currentConfigTs = currentConfig.getCfgPath().lastModified();
-        log.info("Configuration reloaded: "
+          this.currentConfig = newConfig;
+          this.currentConfigTs = lastModified;
+          log.info("Configuration reloaded: "
             + currentConfig.getDestinations(FilterType.ALL).size() + " destinations, "
             + discarded + " replication events discarded");
 
-      } catch (Exception e) {
-        log.error(
-            "Cannot reload replication configuration: keeping existing settings",
-            e);
-        return;
+        }
       }
+    } catch (Exception e) {
+      log.error(
+          "Cannot reload replication configuration: keeping existing settings",
+          e);
+      return;
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
index 4017822..3a0cc3f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
@@ -11,18 +11,20 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package com.googlesource.gerrit.plugins.replication;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.concurrent.atomic.AtomicReference;
 
 public class AutoReloadSecureCredentialsFactoryDecorator implements
@@ -47,32 +49,29 @@
   }
 
   private long getSecureConfigLastEditTs() {
-    FileBasedConfig cfg = new FileBasedConfig(site.secure_config, FS.DETECTED);
-    if (cfg.getFile().exists()) {
-      return cfg.getFile().lastModified();
-    } else {
+    if (!Files.exists(site.secure_config)) {
       return 0L;
     }
+    return lastModified(site.secure_config);
   }
 
   @Override
   public SecureCredentialsProvider create(String remoteName) {
-    if (needsReload()) {
-      try {
+    try {
+      if (needsReload()) {
         secureCredentialsFactory.compareAndSet(secureCredentialsFactory.get(),
             new SecureCredentialsFactory(site));
         secureCredentialsFactoryLoadTs = getSecureConfigLastEditTs();
         log.info("secure.config reloaded as it was updated on the file system");
-      } catch (Exception e) {
-        log.error("Unexpected error while trying to reload "
-            + "secure.config: keeping existing credentials", e);
       }
+    } catch (Exception e) {
+      log.error("Unexpected error while trying to reload "
+          + "secure.config: keeping existing credentials", e);
     }
 
     return secureCredentialsFactory.get().create(remoteName);
   }
 
-
   private boolean needsReload() {
     return config.getConfig().getBoolean("gerrit", "autoReload", false) &&
         getSecureConfigLastEditTs() != secureCredentialsFactoryLoadTs;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
index dfeb174..8744284 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -29,7 +30,6 @@
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.PerThreadRequestScope;
@@ -66,15 +66,16 @@
 
 class Destination {
   private static final Logger repLog = ReplicationQueue.repLog;
-  private static final ReplicationStateLogger stateLog =
-      new ReplicationStateLogger(repLog);
+  private final ReplicationStateListener stateLog;
 
   private final int poolThreads;
   private final String poolName;
 
   private final RemoteConfig remote;
   private final String[] adminUrls;
+  private final String[] urls;
   private final String[] projects;
+  private final String[] authGroupNames;
   private final int delay;
   private final int retryDelay;
   private final Object stateLock = new Object();
@@ -101,15 +102,19 @@
       final RemoteSiteUser.Factory replicationUserFactory,
       final PluginUser pluginUser,
       final GitRepositoryManager gitRepositoryManager,
-      final GroupBackend groupBackend) {
+      final GroupBackend groupBackend,
+      final ReplicationStateListener stateLog) {
     remote = rc;
     gitManager = gitRepositoryManager;
+    this.stateLog = stateLog;
+
     delay = Math.max(0,
         getTimeUnit(rc, cfg, "replicationdelay", 15, TimeUnit.SECONDS));
     retryDelay = Math.max(0,
         getTimeUnit(rc, cfg, "replicationretry", 1, TimeUnit.MINUTES));
     lockErrorMaxRetries = cfg.getInt("replication", "lockErrorMaxRetries", 0);
     adminUrls = cfg.getStringList("remote", rc.getName(), "adminUrl");
+    urls = cfg.getStringList("remote", rc.getName(), "url");
 
     poolThreads = Math.max(0, getInt(rc, cfg, "threads", 1));
     poolName = "ReplicateTo-" + rc.getName();
@@ -124,7 +129,7 @@
     projects = cfg.getStringList("remote", rc.getName(), "projects");
 
     final CurrentUser remoteUser;
-    String[] authGroupNames = cfg.getStringList("remote", rc.getName(), "authGroup");
+    authGroupNames = cfg.getStringList("remote", rc.getName(), "authGroup");
     if (authGroupNames.length > 0) {
       ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
       for (String name : authGroupNames) {
@@ -160,7 +165,7 @@
           final Provider<RequestScopedReviewDbProvider> dbProvider) {
         final RequestContext requestContext = new RequestContext() {
           @Override
-          public CurrentUser getCurrentUser() {
+          public CurrentUser getUser() {
             return remoteUser;
           }
 
@@ -242,27 +247,23 @@
         e = pending.get(uri);
       }
       if (e == null) {
-        Repository git;
-        try {
-          git = gitManager.openRepository(project);
-        } catch (IOException err) {
-          stateLog.error(String.format(
-              "source project %s not available", project), err, state);
-          return;
-        }
-        try {
-          Ref head = git.getRef(Constants.HEAD);
-          if (head != null
-              && head.isSymbolic()
-              && RefNames.REFS_CONFIG.equals(head.getLeaf().getName())) {
+        try (Repository git = gitManager.openRepository(project)) {
+          try {
+            Ref head = git.getRef(Constants.HEAD);
+            if (head != null
+                && head.isSymbolic()
+                && RefNames.REFS_CONFIG.equals(head.getLeaf().getName())) {
+              return;
+            }
+          } catch (IOException err) {
+            stateLog.error(String.format(
+                "cannot check type of project %s", project), err, state);
             return;
           }
         } catch (IOException err) {
           stateLog.error(String.format(
-              "cannot check type of project %s", project), err, state);
+              "source project %s not available", project), err, state);
           return;
-        } finally {
-          git.close();
         }
       }
     }
@@ -507,6 +508,22 @@
     return adminUrls;
   }
 
+  String[] getUrls() {
+    return urls;
+  }
+
+  RemoteConfig getRemoteConfig() {
+    return remote;
+  }
+
+  String[] getAuthGroupNames() {
+    return authGroupNames;
+  }
+
+  String[] getProjects() {
+    return projects;
+  }
+
   int getLockErrorMaxRetries() {
     return lockErrorMaxRetries;
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
new file mode 100644
index 0000000..91fa65a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.replication;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+
+import org.kohsuke.args4j.Option;
+
+import java.util.List;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "list", description = "List specific remote destinations information")
+final class ListCommand extends SshCommand {
+  @Option(name = "--remote", metaVar = "PATTERN", usage = "pattern to match remote name on")
+  private String remote;
+
+  @Option(name = "--detail", usage = "print remote destination detail information")
+  private boolean detail;
+
+  @Option(name = "--json", usage = "output in json format")
+  private boolean json;
+
+  @Inject
+  private ReplicationConfig config;
+
+  @Override
+  protected void run() {
+    List<Destination> dest = config.getDestinations(FilterType.ALL);
+    for (Destination d : dest) {
+      if (matches(d.getRemoteConfig().getName())) {
+        printRemote(d, detail);
+      }
+    }
+  }
+
+  private boolean matches(String name) {
+    return (Strings.isNullOrEmpty(remote)
+        || name.contains(remote)
+        || name.matches(remote));
+  }
+
+  private void addProperty(JsonObject obj, String key, String[] values) {
+    if (values.length > 0) {
+      JsonArray list = new JsonArray();
+      for (String v : values) {
+        list.add(new JsonPrimitive(v));
+      }
+      obj.add(key, list);
+    }
+  }
+
+  private void printRemote(Destination d, boolean detail) {
+    if (json) {
+      JsonObject obj = new JsonObject();
+      obj.addProperty("remote", d.getRemoteConfig().getName());
+      addProperty(obj, "Url", d.getUrls());
+      if (detail) {
+        addProperty(obj, "AdminUrl", d.getAdminUrls());
+        addProperty(obj, "AuthGroup", d.getAuthGroupNames());
+        addProperty(obj, "Project", d.getProjects());
+      }
+      stdout.print(obj.toString() + "\n");
+    } else {
+      StringBuilder out = new StringBuilder();
+      out.append("Remote: ")
+        .append(d.getRemoteConfig().getName())
+        .append("\n");
+      for (String url : d.getUrls()) {
+        out.append("Url: ")
+          .append(url)
+          .append("\n");
+      }
+
+      if (detail) {
+        for (String adminUrl : d.getAdminUrls()) {
+          out.append("AdminUrl: ")
+            .append(adminUrl)
+            .append("\n");
+        }
+
+        for (String authGroup : d.getAuthGroupNames()) {
+          out.append("AuthGroup: ")
+            .append(authGroup)
+            .append("\n");
+        }
+
+        for (String project : d.getProjects()) {
+          out.append("Project: ")
+            .append(project)
+            .append("\n");
+        }
+      }
+      stdout.print(out.toString() + "\n");
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
index c6ad873..5ff4035 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
@@ -25,8 +25,7 @@
 import java.util.concurrent.TimeUnit;
 
 class PushAll implements Runnable {
-  private static final ReplicationStateLogger stateLog =
-      new ReplicationStateLogger(ReplicationQueue.repLog);
+  private final ReplicationStateListener stateLog;
 
   interface Factory {
     PushAll create(String urlMatch,
@@ -45,12 +44,14 @@
   PushAll(WorkQueue wq,
       ProjectCache projectCache,
       ReplicationQueue rq,
+      ReplicationStateListener stateLog,
       @Assisted @Nullable String urlMatch,
       @Assisted ReplicationFilter filter,
       @Assisted ReplicationState state) {
     this.workQueue = wq;
     this.projectCache = projectCache;
     this.replication = rq;
+    this.stateLog = stateLog;
     this.urlMatch = urlMatch;
     this.filter = filter;
     this.state = state;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
index 255c51b..4a2d01b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -14,6 +14,8 @@
 
 package com.googlesource.gerrit.plugins.replication;
 
+import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
+
 import com.google.common.base.Throwables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Lists;
@@ -59,7 +61,6 @@
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
-import org.slf4j.Logger;
 import org.slf4j.MDC;
 
 import java.io.IOException;
@@ -78,9 +79,7 @@
  * take that lock to ensure they are working with a current view of the object.
  */
 class PushOne implements ProjectRunnable {
-  private static final Logger repLog = ReplicationQueue.repLog;
-  private static final ReplicationStateLogger stateLog =
-      new ReplicationStateLogger(repLog);
+  private final ReplicationStateListener stateLog;
   static final String ALL_REFS = "..all..";
   static final String ID_MDC_KEY = "pushOneId";
 
@@ -124,6 +123,7 @@
       final ChangeCache cc,
       final ReplicationQueue rq,
       final IdGenerator ig,
+      final ReplicationStateListener sl,
       @Assisted final Project.NameKey d,
       @Assisted final URIish u) {
     gitManager = grm;
@@ -140,6 +140,7 @@
     lockRetryCount = 0;
     maxLockRetries = pool.getLockErrorMaxRetries();
     id = ig.next();
+    stateLog = sl;
     createdAt = TimeUtil.nowMs();
   }
 
@@ -243,7 +244,7 @@
     if (!stateMap.isEmpty() && !isRetrying()) {
       for (Map.Entry<String,ReplicationState> entry : stateMap.entries()) {
         entry.getValue().notifyRefReplicated(projectName.get(), entry.getKey(), uri,
-            RefPushResult.FAILED);
+            RefPushResult.FAILED, null);
       }
     }
   }
@@ -427,19 +428,13 @@
         local = n;
       }
 
-      ReviewDb db;
-      try {
-        db = schema.open();
+      try (ReviewDb db = schema.open()) {
+        local = new VisibleRefFilter(tagCache, changeCache, git, pc, db, true)
+            .filter(local, true);
       } catch (OrmException e) {
         stateLog.error("Cannot read database to replicate to " + projectName, e, getStatesAsArray());
         return Collections.emptyList();
       }
-      try {
-        local = new VisibleRefFilter(tagCache, changeCache, git, pc, db, true)
-            .filter(local, true);
-      } finally {
-        db.close();
-      }
     }
 
     return pushAllRefs ? doPushAll(tn, local) : doPushDelta(local);
@@ -549,6 +544,7 @@
       throws LockFailureException {
     Set<String> doneRefs = new HashSet<>();
     boolean anyRefFailed = false;
+    RemoteRefUpdate.Status lastRefStatusError = RemoteRefUpdate.Status.OK;
 
     for (RemoteRefUpdate u : refUpdates) {
       RefPushResult pushStatus = RefPushResult.SUCCEEDED;
@@ -574,6 +570,7 @@
               u.getRemoteName(), uri, u.getStatus()), logStatesArray);
           pushStatus = RefPushResult.FAILED;
           anyRefFailed = true;
+          lastRefStatusError = u.getStatus();
           break;
 
         case REJECTED_OTHER_REASON:
@@ -591,30 +588,31 @@
           }
           pushStatus = RefPushResult.FAILED;
           anyRefFailed = true;
+          lastRefStatusError = u.getStatus();
           break;
       }
 
       for (ReplicationState rs : getStatesByRef(u.getSrcRef())) {
         rs.notifyRefReplicated(projectName.get(), u.getSrcRef(),
-              uri, pushStatus);
+              uri, pushStatus, u.getStatus());
       }
     }
 
     doneRefs.add(ALL_REFS);
     for (ReplicationState rs : getStatesByRef(ALL_REFS)) {
-      rs.notifyRefReplicated(projectName.get(), ALL_REFS,
-          uri, anyRefFailed ? RefPushResult.FAILED : RefPushResult.SUCCEEDED);
+      rs.notifyRefReplicated(projectName.get(), ALL_REFS, uri, anyRefFailed
+          ? RefPushResult.FAILED : RefPushResult.SUCCEEDED, lastRefStatusError);
     }
-    for (Map.Entry<String,ReplicationState> entry : stateMap.entries()) {
+    for (Map.Entry<String, ReplicationState> entry : stateMap.entries()) {
       if (!doneRefs.contains(entry.getKey())) {
-        entry.getValue().notifyRefReplicated(projectName.get(), entry.getKey(), uri,
-            RefPushResult.NOT_ATTEMPTED);
+        entry.getValue().notifyRefReplicated(projectName.get(), entry.getKey(),
+            uri, RefPushResult.NOT_ATTEMPTED, null);
       }
     }
     stateMap.clear();
   }
 
-  public class LockFailureException extends TransportException {
+  public static class LockFailureException extends TransportException {
     private static final long serialVersionUID = 1L;
 
     public LockFailureException(URIish uri, String message) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java
index 0f8cc9e..c63d346 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java
@@ -26,6 +26,7 @@
 
 import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,7 +36,8 @@
 
 public abstract class PushResultProcessing {
 
-  abstract void onRefReplicatedToOneNode(String project, String ref, URIish uri, RefPushResult status);
+  abstract void onRefReplicatedToOneNode(String project, String ref,
+      URIish uri, RefPushResult status, RemoteRefUpdate.Status refStatus);
 
   abstract void onRefReplicatedToAllNodes(String project, String ref, int nodesCount);
 
@@ -83,7 +85,7 @@
 
     @Override
     void onRefReplicatedToOneNode(String project, String ref, URIish uri,
-        RefPushResult status) {
+        RefPushResult status, RemoteRefUpdate.Status refStatus) {
       StringBuilder sb = new StringBuilder();
       sb.append("Replicate ");
       sb.append(project);
@@ -107,6 +109,9 @@
           sb.append("UNKNOWN RESULT!");
           break;
       }
+      sb.append(" (");
+      sb.append(refStatus.toString());
+      sb.append(")");
       writeStdOut(sb.toString());
     }
 
@@ -167,9 +172,9 @@
 
     @Override
     void onRefReplicatedToOneNode(String project, String ref, URIish uri,
-        RefPushResult status) {
+        RefPushResult status, RemoteRefUpdate.Status refStatus) {
       RefReplicatedEvent event =
-          new RefReplicatedEvent(project, ref, resolveNodeName(uri), status);
+          new RefReplicatedEvent(project, ref, resolveNodeName(uri), status, refStatus);
       postEvent(project, ref, event);
     }
 
@@ -185,16 +190,11 @@
     }
 
     private void postEvent(String project, String ref, RefEvent event) {
-      if (PatchSet.isRef(ref)) {
-        try {
-          ReviewDb db = schema.open();
-          try {
-            Change change = retrieveChange(ref, db);
-            if (change != null) {
-              dispatcher.postEvent(change, event, db);
-            }
-          } finally {
-            db.close();
+      if (PatchSet.isChangeRef(ref)) {
+        try (ReviewDb db = schema.open()) {
+          Change change = retrieveChange(ref, db);
+          if (change != null) {
+            dispatcher.postEvent(change, event, db);
           }
         } catch (Exception e) {
           log.error("Cannot post event", e);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
index 664e819..f200194 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
@@ -19,19 +19,24 @@
 
 import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
 public class RefReplicatedEvent extends RefEvent {
   public final String project;
   public final String ref;
   public final String targetNode;
   public final String status;
+  public final Status refStatus;
 
   public RefReplicatedEvent(String project, String ref, String targetNode,
-      RefPushResult status) {
+      RefPushResult status, RemoteRefUpdate.Status refStatus) {
     super("ref-replicated");
     this.project = project;
     this.ref = ref;
     this.targetNode = targetNode;
     this.status = toStatusString(status);
+    this.refStatus = refStatus;
   }
 
   private String toStatusString(RefPushResult status) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
index f56185d..1486f1b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
@@ -35,9 +35,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -46,7 +46,7 @@
 public class ReplicationFileBasedConfig implements ReplicationConfig {
   static final Logger log = LoggerFactory.getLogger(ReplicationFileBasedConfig.class);
   private List<Destination> destinations;
-  private File cfgPath;
+  private Path cfgPath;
   private boolean replicateAllOnPluginStart;
   private boolean defaultForceUpdate;
   private Injector injector;
@@ -55,20 +55,23 @@
   private final GitRepositoryManager gitRepositoryManager;
   private final GroupBackend groupBackend;
   private final FileBasedConfig config;
+  private final ReplicationStateListener stateLog;
 
   @Inject
   public ReplicationFileBasedConfig(final Injector injector, final SitePaths site,
       final RemoteSiteUser.Factory ruf, final PluginUser pu,
       final GitRepositoryManager grm,
-      final GroupBackend gb) throws ConfigInvalidException, IOException {
-    this.cfgPath = new File(site.etc_dir, "replication.config");
+      final GroupBackend gb,
+      final ReplicationStateListener stateLog) throws ConfigInvalidException, IOException {
+    this.cfgPath = site.etc_dir.resolve("replication.config");
     this.injector = injector;
     this.replicationUserFactory = ruf;
     this.pluginUser = pu;
     this.gitRepositoryManager = grm;
     this.groupBackend = gb;
-    this.config = new FileBasedConfig(cfgPath, FS.DETECTED);
+    this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED);
     this.destinations = allDestinations();
+    this.stateLog = stateLog;
   }
 
   /*
@@ -115,7 +118,7 @@
   private List<Destination> allDestinations()
       throws ConfigInvalidException, IOException {
     if (!config.getFile().exists()) {
-      log.warn("Config file " + config.getFile() + "does not exist; not replicating");
+      log.warn("Config file " + config.getFile() + " does not exist; not replicating");
       return Collections.emptyList();
     }
     if (config.getFile().length() == 0) {
@@ -159,7 +162,7 @@
 
       Destination destination =
           new Destination(injector, c, config, replicationUserFactory,
-              pluginUser, gitRepositoryManager, groupBackend);
+              pluginUser, gitRepositoryManager, groupBackend, stateLog);
 
       if (!destination.isSingleProjectMatch()) {
         for (URIish u : c.getURIs()) {
@@ -215,7 +218,7 @@
     return destinations.isEmpty();
   }
 
-  File getCfgPath() {
+  Path getCfgPath() {
     return cfgPath;
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java
index 50f1b8b..fcc3437 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java
@@ -14,55 +14,19 @@
 
 package com.googlesource.gerrit.plugins.replication;
 
-import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
+import com.google.gerrit.server.util.PluginLogFile;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.inject.Inject;
 
-import org.apache.log4j.AsyncAppender;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
 
-public class ReplicationLogFile implements LifecycleListener {
-
-  private final SystemLog systemLog;
-  private final ServerInformation serverInfo;
-  private static boolean started;
+public class ReplicationLogFile extends PluginLogFile {
 
   @Inject
-  public ReplicationLogFile(final SystemLog systemLog,
+  public ReplicationLogFile(SystemLog systemLog,
       ServerInformation serverInfo) {
-    this.systemLog = systemLog;
-    this.serverInfo = serverInfo;
-  }
-
-  @Override
-  public void start() {
-    if (!started) {
-      Logger replicationLogger =
-          LogManager.getLogger(ReplicationQueue.REPLICATION_LOG_NAME);
-      String loggerName = replicationLogger.getName();
-      AsyncAppender asyncAppender = systemLog.createAsyncAppender(
-          loggerName, new PatternLayout("[%d] [%X{"
-              + PushOne.ID_MDC_KEY + "}] %m%n"));
-      replicationLogger.removeAppender(loggerName);
-      replicationLogger.addAppender(asyncAppender);
-      replicationLogger.setAdditivity(false);
-      started = true;
-    }
-  }
-
-  @Override
-  public void stop() {
-    // stop is called when plugin is unloaded or when the server shutdown.
-    // Only clean up when the server is shutting down to prevent issue when a
-    // plugin is reloaded. The issue is that gerrit load the new plugin and then
-    // unload the old one so because loggers are static, the unload of the old
-    // plugin would remove the appenders just created by the new plugin.
-    if (serverInfo.getState() == ServerInformation.State.SHUTDOWN) {
-      LogManager.getLogger(ReplicationQueue.repLog.getName())
-          .removeAllAppenders();
-    }
+    super(systemLog, serverInfo, ReplicationQueue.REPLICATION_LOG_NAME,
+        new PatternLayout("[%d] [%X{" + PushOne.ID_MDC_KEY + "}] %m%n"));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
index b9575b9..a5d0b82 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
@@ -31,6 +31,8 @@
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 import com.google.inject.internal.UniqueAnnotations;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+
 
 class ReplicationModule extends AbstractModule {
   @Override
@@ -62,8 +64,10 @@
     install(new FactoryModuleBuilder().build(RemoteSiteUser.Factory.class));
 
     bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class);
+    bind(ReplicationStateListener.class).to(ReplicationStateLogger.class);
 
-    EventTypes.registerClass(new RefReplicatedEvent(null, null, null, SUCCEEDED));
+    EventTypes.registerClass(new RefReplicatedEvent(null, null, null,
+        SUCCEEDED, RemoteRefUpdate.Status.OK));
     EventTypes.registerClass(new RefReplicationDoneEvent(null, null, 0));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
index 95693d9..67d172f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -63,8 +63,8 @@
   static final String REPLICATION_LOG_NAME = "replication_log";
   static final Logger repLog = LoggerFactory.getLogger(REPLICATION_LOG_NAME);
   private static final int SSH_REMOTE_TIMEOUT = 120 * 1000;
-  private static final ReplicationStateLogger stateLog =
-      new ReplicationStateLogger(repLog);
+
+  private final ReplicationStateListener stateLog;
 
   static String replaceName(String in, String name, boolean keyIsOptional) {
     String key = "${name}";
@@ -85,12 +85,16 @@
   private volatile boolean running;
 
   @Inject
-  ReplicationQueue(final WorkQueue wq, final ReplicationConfig rc,
-      final SchemaFactory<ReviewDb> db, final EventDispatcher dis) {
+  ReplicationQueue(final WorkQueue wq,
+      final ReplicationConfig rc,
+      final SchemaFactory<ReviewDb> db,
+      final EventDispatcher dis,
+      final ReplicationStateListener sl) {
     workQueue = wq;
     database = db;
     dispatcher = dis;
     config = rc;
+    stateLog = sl;
   }
 
   @Override
@@ -256,18 +260,13 @@
   }
 
   private static void createLocally(URIish uri, String head) {
-    try {
-      Repository repo = new FileRepository(uri.getPath());
-      try {
-        repo.create(true /* bare */);
+    try (Repository repo = new FileRepository(uri.getPath())) {
+      repo.create(true /* bare */);
 
-        if (head != null) {
-          RefUpdate u = repo.updateRef(Constants.HEAD);
-          u.disableRefLog();
-          u.link(head);
-        }
-      } finally {
-        repo.close();
+      if (head != null) {
+        RefUpdate u = repo.updateRef(Constants.HEAD);
+        u.disableRefLog();
+        u.link(head);
       }
     } catch (IOException e) {
       repLog.error(String.format(
@@ -385,15 +384,10 @@
   }
 
   private static void updateHeadLocally(URIish uri, String newHead) {
-    try {
-      Repository repo = new FileRepository(uri.getPath());
-      try {
-        if (newHead != null) {
-          RefUpdate u = repo.updateRef(Constants.HEAD);
-          u.link(newHead);
-        }
-      } finally {
-        repo.close();
+    try (Repository repo = new FileRepository(uri.getPath())) {
+      if (newHead != null) {
+        RefUpdate u = repo.updateRef(Constants.HEAD);
+        u.link(newHead);
       }
     } catch (IOException e) {
       repLog.error(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java
index c851db6..b29456d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Table;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 
 import java.util.concurrent.CountDownLatch;
@@ -69,8 +70,9 @@
   }
 
   public void notifyRefReplicated(String project, String ref, URIish uri,
-      RefPushResult status) {
-    pushResultProcessing.onRefReplicatedToOneNode(project, ref, uri, status);
+      RefPushResult status, RemoteRefUpdate.Status refUpdateStatus) {
+    pushResultProcessing.onRefReplicatedToOneNode(project, ref, uri, status,
+        refUpdateStatus);
 
     RefReplicationStatus completedRefStatus = null;
     boolean allPushTaksCompleted = false;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java
new file mode 100644
index 0000000..e5ac9d5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.replication;
+
+/**
+ * Interface for notifying replication status updates.
+ */
+public interface ReplicationStateListener {
+
+  /**
+   * Notify a non-fatal replication error.
+   *
+   * Replication states received a non-fatal error with an associated
+   * warning message.
+   *
+   * @param msg message description of the error
+   * @param states replication states impacted
+   */
+  public abstract void warn(String msg, ReplicationState... states);
+
+  /**
+   * Notify a fatal replication error.
+   *
+   * Replication states have received a fatal error and replication has
+   * failed.
+   *
+   * @param msg message description of the error
+   * @param states replication states impacted
+   */
+  public abstract void error(String msg, ReplicationState... states);
+
+  /**
+   * Notify a fatal replication error with the associated exception.
+   *
+   * Replication states have received a fatal exception and replication has failed.
+   *
+   * @param msg message description of the error
+   * @param t exception that caused the replication to fail
+   * @param states replication states impacted
+   */
+  public abstract void error(String msg, Throwable t,
+      ReplicationState... states);
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java
index cb1d4ce..0a59ad3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java
@@ -14,7 +14,9 @@
 
 package com.googlesource.gerrit.plugins.replication;
 
-import org.slf4j.Logger;
+import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
+
+import com.google.inject.Singleton;
 
 /**
  * Wrapper around a Logger that also logs out the replication state.
@@ -24,32 +26,25 @@
  * and logs additional information about the replication state to the
  * stderr console.
  */
-public class ReplicationStateLogger {
+@Singleton
+class ReplicationStateLogger implements ReplicationStateListener {
 
-  private final Logger logger;
-
-  public ReplicationStateLogger(Logger logger) {
-    this.logger = logger;
-  }
-
+  @Override
   public void warn(String msg, ReplicationState... states) {
     stateWriteErr("Warning: " + msg, states);
-    logger.warn(msg);
+    repLog.warn(msg);
   }
 
-  public void warn(String msg, Throwable t, ReplicationState... states) {
-    stateWriteErr("Warning: " + msg, states);
-    logger.warn(msg, t);
-  }
-
+  @Override
   public void error(String msg, ReplicationState... states) {
     stateWriteErr("Error: " + msg, states);
-    logger.error(msg);
+    repLog.error(msg);
   }
 
+  @Override
   public void error(String msg, Throwable t, ReplicationState... states) {
     stateWriteErr("Error: " + msg, states);
-    logger.error(msg, t);
+    repLog.error(msg, t);
   }
 
   private void stateWriteErr(String msg, ReplicationState[] states) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java
index 32d3905..a10f62f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java
@@ -36,7 +36,8 @@
 
   private static Config load(SitePaths site)
       throws ConfigInvalidException, IOException {
-    FileBasedConfig cfg = new FileBasedConfig(site.secure_config, FS.DETECTED);
+    FileBasedConfig cfg =
+        new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED);
     if (cfg.getFile().exists() && cfg.getFile().length() > 0) {
       try {
         cfg.load();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
index dd33049..0cab7b1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
@@ -20,5 +20,6 @@
   @Override
   protected void configureCommands() {
     command(StartCommand.class);
+    command(ListCommand.class);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
index 17a2df9..be5242e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
@@ -23,8 +23,6 @@
 
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -35,8 +33,9 @@
 @RequiresCapability(StartReplicationCapability.START_REPLICATION)
 @CommandMetaData(name = "start", description = "Start replication for specific project or all projects")
 final class StartCommand extends SshCommand {
-  private static final Logger log = LoggerFactory.getLogger(StartCommand.class);
-  private static final ReplicationStateLogger stateLog = new ReplicationStateLogger(log);
+  @Inject
+  private ReplicationStateLogger stateLog;
+
   @Option(name = "--all", usage = "push all known projects")
   private boolean all;
 
diff --git a/src/main/resources/Documentation/cmd-list.md b/src/main/resources/Documentation/cmd-list.md
new file mode 100644
index 0000000..84bb56b
--- /dev/null
+++ b/src/main/resources/Documentation/cmd-list.md
@@ -0,0 +1,76 @@
+@PLUGIN@ list
+==============
+
+NAME
+----
+@PLUGIN@ list - List specific remote destinations information
+
+SYNOPSIS
+--------
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list
+  [--remote <PATTERN>]
+  [--detail]
+  [--json]
+```
+
+DESCRIPTION
+-----------
+List all remote destinations information, or only those whose
+name match the pattern given on the command line.
+
+ACCESS
+------
+Caller must be a member of the privileged 'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts. It is very useful
+for replication status check for administrators as well.
+
+OPTIONS
+-------
+
+`--remote <PATTERN>`
+:	Only print destinations whose remote name contains
+	the substring `PATTERN`.
+
+`--detail`
+:	Print remote detail information: Name, Url, AdminUrl,
+	AuthGroup and Project.
+
+`--json`
+:	Output in json format.
+
+EXAMPLES
+--------
+List all destinations:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list
+```
+
+List all destinations detail information:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list --detail
+```
+
+List all destinations detail information in json format:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list --detail --json
+```
+
+List destinations whose name contains mirror:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list --remote mirror
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ list --remote ^.*mirror.*
+```
+
+SEE ALSO
+--------
+
+* [Replication Configuration](config.html)
+* [Access Control](../../../Documentation/access-control.html)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
index a400dca..c6b2eb6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
@@ -39,6 +39,7 @@
 
 import junit.framework.TestCase;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 
 import java.net.URISyntaxException;
@@ -73,14 +74,15 @@
     reset(dispatcherMock);
     RefReplicatedEvent expectedEvent =
         new RefReplicatedEvent("someProject", "refs/heads/master", "someHost",
-            RefPushResult.SUCCEEDED);
+            RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     dispatcherMock.postEvent(anyObject(Branch.NameKey.class),
         RefReplicatedEventEquals.eqEvent(expectedEvent));
     expectLastCall().once();
     replay(dispatcherMock);
 
-    gitUpdateProcessing.onRefReplicatedToOneNode("someProject", "refs/heads/master",
-        new URIish("git://someHost/someProject.git"), RefPushResult.SUCCEEDED);
+    gitUpdateProcessing.onRefReplicatedToOneNode("someProject",
+        "refs/heads/master", new URIish("git://someHost/someProject.git"),
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     verify(dispatcherMock);
   }
 
@@ -93,7 +95,7 @@
     reset(dispatcherMock);
     RefReplicatedEvent expectedEvent =
         new RefReplicatedEvent("someProject", "refs/changes/01/1/1", "someHost",
-            RefPushResult.FAILED);
+            RefPushResult.FAILED, RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD);
     dispatcherMock.postEvent(eq(expectedChange),
         RefReplicatedEventEquals.eqEvent(expectedEvent),
         anyObject(ReviewDb.class));
@@ -102,7 +104,7 @@
 
     gitUpdateProcessing.onRefReplicatedToOneNode("someProject",
         "refs/changes/01/1/1", new URIish("git://someHost/someProject.git"),
-        RefPushResult.FAILED);
+        RefPushResult.FAILED, RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD);
     verify(dispatcherMock);
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
index a0bf576..1408ed6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
@@ -23,6 +23,7 @@
 
 import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
 
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 import org.junit.Before;
 import org.junit.Test;
@@ -73,7 +74,7 @@
 
     //expected events
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "someRef",
-        uri, RefPushResult.SUCCEEDED);
+        uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToAllNodes("someProject",
         "someRef", 1);
     pushResultProcessingMock.onAllRefsReplicatedToAllNodes(1);
@@ -83,7 +84,7 @@
     replicationState.increasePushTaskCount("someProject", "someRef");
     replicationState.markAllPushTasksScheduled();
     replicationState.notifyRefReplicated("someProject", "someRef", uri,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     verify(pushResultProcessingMock);
   }
 
@@ -96,9 +97,9 @@
 
     //expected events
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "someRef",
-        uri1, RefPushResult.SUCCEEDED);
+        uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "someRef",
-        uri2, RefPushResult.FAILED);
+        uri2, RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING);
     pushResultProcessingMock.onRefReplicatedToAllNodes("someProject",
         "someRef", 2);
     pushResultProcessingMock.onAllRefsReplicatedToAllNodes(2);
@@ -109,9 +110,9 @@
     replicationState.increasePushTaskCount("someProject", "someRef");
     replicationState.markAllPushTasksScheduled();
     replicationState.notifyRefReplicated("someProject", "someRef", uri1,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "someRef", uri2,
-        RefPushResult.FAILED);
+        RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING);
     verify(pushResultProcessingMock);
   }
 
@@ -125,15 +126,15 @@
 
     //expected events
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref1",
-        uri1, RefPushResult.SUCCEEDED);
+        uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref1",
-        uri2, RefPushResult.SUCCEEDED);
+        uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref1",
-        uri3, RefPushResult.SUCCEEDED);
+        uri3, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref2",
-        uri1, RefPushResult.SUCCEEDED);
+        uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref2",
-        uri2, RefPushResult.SUCCEEDED);
+        uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock
         .onRefReplicatedToAllNodes("someProject", "ref1", 3);
     pushResultProcessingMock
@@ -149,15 +150,15 @@
     replicationState.increasePushTaskCount("someProject", "ref2");
     replicationState.markAllPushTasksScheduled();
     replicationState.notifyRefReplicated("someProject", "ref1", uri1,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "ref1", uri2,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "ref1", uri3,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "ref2", uri1,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "ref2", uri2,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     verify(pushResultProcessingMock);
   }
 
@@ -169,9 +170,9 @@
 
     //expected events
     pushResultProcessingMock.onRefReplicatedToOneNode("project1", "ref1", uri,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("project2", "ref2", uri,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToAllNodes("project1", "ref1", 1);
     pushResultProcessingMock.onRefReplicatedToAllNodes("project2", "ref2", 1);
     pushResultProcessingMock.onAllRefsReplicatedToAllNodes(2);
@@ -182,9 +183,9 @@
     replicationState.increasePushTaskCount("project2", "ref2");
     replicationState.markAllPushTasksScheduled();
     replicationState.notifyRefReplicated("project1", "ref1", uri,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("project2", "ref2", uri,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     verify(pushResultProcessingMock);
   }
 
@@ -196,9 +197,9 @@
 
    //expected events
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref1",
-        uri1, RefPushResult.SUCCEEDED);
+        uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock.onRefReplicatedToOneNode("someProject", "ref2",
-        uri1, RefPushResult.SUCCEEDED);
+        uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     pushResultProcessingMock
         .onRefReplicatedToAllNodes("someProject", "ref1", 1);
     pushResultProcessingMock
@@ -210,9 +211,9 @@
     replicationState.increasePushTaskCount("someProject", "ref1");
     replicationState.increasePushTaskCount("someProject", "ref2");
     replicationState.notifyRefReplicated("someProject", "ref1", uri1,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.notifyRefReplicated("someProject", "ref2", uri1,
-        RefPushResult.SUCCEEDED);
+        RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
     replicationState.markAllPushTasksScheduled();
     verify(pushResultProcessingMock);
   }