Merge branch 'stable-2.13'

* stable-2.13:
  ReplicationMetrics: Make members private final

Change-Id: Icf7dd3f5c9dd518c8984546aba9708525a428f79
diff --git a/.gitignore b/.gitignore
index 9c143f3..1e8377d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,4 @@
 /.settings/org.eclipse.m2e.core.prefs
 /.idea
 replication.iml
-/.buckd
-/buck-cache
-/buck-out
+*.iml
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 17904c0..1792fcc 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
 #Fri Jul 16 23:39:13 PDT 2010
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/BUCK b/BUCK
deleted file mode 100644
index d658b92..0000000
--- a/BUCK
+++ /dev/null
@@ -1,40 +0,0 @@
-include_defs('//lib/maven.defs')
-
-gerrit_plugin(
-  name = 'replication',
-  srcs = glob(['src/main/java/**/*.java']),
-  resources = glob(['src/main/resources/**/*']),
-  manifest_entries = [
-    'Implementation-Title: Replication plugin',
-    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication',
-    'Gerrit-PluginName: replication',
-    'Gerrit-Module: com.googlesource.gerrit.plugins.replication.ReplicationModule',
-    'Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule'
-  ],
-  deps = [
-    ':commons-io',
-  ],
-  provided_deps = [
-    '//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-acceptance-framework:lib',
-    '//gerrit-plugin-api:lib',
-  ],
-)
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..eb92f6d
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,45 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+load("//tools/bzl:plugin.bzl", "gerrit_plugin")
+
+gerrit_plugin(
+    name = "replication",
+    srcs = glob(["src/main/java/**/*.java"]),
+    manifest_entries = [
+        "Implementation-Title: Replication plugin",
+        "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication",
+        "Gerrit-PluginName: replication",
+        "Gerrit-Module: com.googlesource.gerrit.plugins.replication.ReplicationModule",
+        "Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule",
+    ],
+    resources = glob(["src/main/resources/**/*"]),
+    deps = [
+        "//lib:commons-io",
+    ],
+)
+
+junit_tests(
+    name = "replication_tests",
+    srcs = glob(["src/test/java/**/*Test.java"]),
+    tags = ["replication"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":replication__plugin",
+        ":replication_util",
+        "//gerrit-acceptance-framework:lib",
+        "//gerrit-plugin-api:lib",
+    ],
+)
+
+java_library(
+    name = "replication_util",
+    testonly = 1,
+    srcs = glob(
+        ["src/test/java/**/*.java"],
+        exclude = ["src/test/java/**/*Test.java"],
+    ),
+    deps = [
+        ":replication__plugin",
+        "//gerrit-acceptance-framework:lib",
+        "//gerrit-plugin-api:lib",
+    ],
+)
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 22b29b1..d9d3901 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
@@ -14,14 +14,9 @@
 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.account.GroupIncludeCache;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -38,38 +33,20 @@
   private ReplicationFileBasedConfig currentConfig;
   private long currentConfigTs;
 
-  private final Injector injector;
   private final SitePaths site;
-  private final RemoteSiteUser.Factory remoteSiteUserFactory;
-  private final PluginUser pluginUser;
-  private final GitRepositoryManager gitRepositoryManager;
-  private final GroupBackend groupBackend;
   private final WorkQueue workQueue;
-  private final ReplicationStateListener stateLog;
-  private final GroupIncludeCache groupIncludeCache;
+  private final DestinationFactory destinationFactory;
 
   @Inject
-  public AutoReloadConfigDecorator(Injector injector,
-      SitePaths site,
-      RemoteSiteUser.Factory ruf,
-      PluginUser pu,
-      GitRepositoryManager grm,
-      GroupBackend gb,
+  public AutoReloadConfigDecorator(SitePaths site,
       WorkQueue workQueue,
-      ReplicationStateListener stateLog,
-      GroupIncludeCache groupIncludeCache)
+      DestinationFactory destinationFactory)
       throws ConfigInvalidException, IOException {
-    this.injector = injector;
     this.site = site;
-    this.remoteSiteUserFactory = ruf;
-    this.pluginUser = pu;
-    this.gitRepositoryManager = grm;
-    this.groupBackend = gb;
-    this.groupIncludeCache = groupIncludeCache;
+    this.destinationFactory = destinationFactory;
     this.currentConfig = loadConfig();
     this.currentConfigTs = getLastModified(currentConfig);
     this.workQueue = workQueue;
-    this.stateLog = stateLog;
   }
 
   private static long getLastModified(ReplicationFileBasedConfig cfg) {
@@ -78,9 +55,7 @@
 
   private ReplicationFileBasedConfig loadConfig()
       throws ConfigInvalidException, IOException {
-    return new ReplicationFileBasedConfig(injector, site, remoteSiteUserFactory,
-        pluginUser, gitRepositoryManager, groupBackend, stateLog,
-        groupIncludeCache);
+    return new ReplicationFileBasedConfig(site, destinationFactory);
   }
 
   private synchronized boolean isAutoReload() {
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 06cbe33..5e9f01c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -14,15 +14,20 @@
 
 package com.googlesource.gerrit.plugins.replication;
 
+import static com.googlesource.gerrit.plugins.replication.PushResultProcessing.resolveNodeName;
+
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.EventDispatcher;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -76,6 +81,7 @@
   private volatile WorkQueue.Executor pool;
   private final PerThreadRequestScope.Scoper threadScoper;
   private final DestinationConfiguration config;
+  private final DynamicItem<EventDispatcher> eventDispatcher;
 
   protected enum RetryReason {
     TRANSPORT_ERROR, COLLISION, REPOSITORY_MISSING;
@@ -99,8 +105,10 @@
       GitRepositoryManager gitRepositoryManager,
       GroupBackend groupBackend,
       ReplicationStateListener stateLog,
-      GroupIncludeCache groupIncludeCache) {
+      GroupIncludeCache groupIncludeCache,
+      DynamicItem<EventDispatcher> eventDispatcher) {
     config = cfg;
+    this.eventDispatcher = eventDispatcher;
     gitManager = gitRepositoryManager;
     this.stateLog = stateLog;
 
@@ -220,7 +228,8 @@
       stateLog.error(String.format("source project %s not available", project),
           err, states);
     } catch (Exception e) {
-      throw Throwables.propagate(e);
+      Throwables.throwIfUnchecked(e);
+      throw new RuntimeException(e);
     }
     return false;
   }
@@ -238,7 +247,7 @@
       stateLog.error(String.format("source project %s not available", project),
           err, states);
     } catch (Exception e) {
-      Throwables.propagateIfPossible(e);
+      Throwables.throwIfUnchecked(e);
       throw new RuntimeException(e);
     }
     return false;
@@ -282,10 +291,12 @@
       PushOne e = pending.get(uri);
       if (e == null) {
         e = opFactory.create(project, uri);
+        addRef(e, ref);
         pool.schedule(e, config.getDelay(), TimeUnit.SECONDS);
         pending.put(uri, e);
+      } else if (!e.getRefs().contains(ref)) {
+        addRef(e, ref);
       }
-      e.addRef(ref);
       state.increasePushTaskCount(project.get(), ref);
       e.addState(ref, state);
       repLog.info("scheduled {}:{} => {} to run after {}s", project, ref,
@@ -300,6 +311,11 @@
     }
   }
 
+  private void addRef(PushOne e, String ref) {
+    e.addRef(ref);
+    postEvent(e, ref);
+  }
+
   /**
    * It schedules again a PushOp instance.
    * <p>
@@ -555,4 +571,12 @@
     }
     return uri.toString().contains(urlMatch);
   }
+
+  private void postEvent(PushOne pushOp, String ref) {
+    Project.NameKey project = pushOp.getProjectNameKey();
+    String targetNode = resolveNodeName(pushOp.getURI());
+    ReplicationScheduledEvent event =
+        new ReplicationScheduledEvent(project.get(), ref, targetNode);
+    eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java
new file mode 100644
index 0000000..970a018
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 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.gerrit.common.EventDispatcher;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupIncludeCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+
+@Singleton
+public class DestinationFactory {
+  private final Injector injector;
+  private final RemoteSiteUser.Factory replicationUserFactory;
+  private final PluginUser pluginUser;
+  private final GitRepositoryManager gitRepositoryManager;
+  private final GroupBackend groupBackend;
+  private final ReplicationStateListener stateLog;
+  private final GroupIncludeCache groupIncludeCache;
+  private final DynamicItem<EventDispatcher> eventDispatcher;
+
+  @Inject
+  public DestinationFactory(Injector injector,
+      RemoteSiteUser.Factory replicationUserFactory,
+      PluginUser pluginUser,
+      GitRepositoryManager gitRepositoryManager,
+      GroupBackend groupBackend,
+      ReplicationStateListener stateLog,
+      GroupIncludeCache groupIncludeCache,
+      DynamicItem<EventDispatcher> eventDispatcher) {
+    this.injector = injector;
+    this.replicationUserFactory = replicationUserFactory;
+    this.pluginUser = pluginUser;
+    this.gitRepositoryManager = gitRepositoryManager;
+    this.groupBackend = groupBackend;
+    this.stateLog = stateLog;
+    this.groupIncludeCache = groupIncludeCache;
+    this.eventDispatcher = eventDispatcher;
+  }
+
+  Destination create(DestinationConfiguration config) {
+    return new Destination(injector, config, replicationUserFactory, pluginUser,
+        gitRepositoryManager, groupBackend, stateLog, groupIncludeCache,
+        eventDispatcher);
+  }
+}
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 a251832..6ec9cf8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -19,7 +19,7 @@
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Multimap;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
@@ -111,7 +111,7 @@
   private boolean retrying;
   private int retryCount;
   private boolean canceled;
-  private final Multimap<String,ReplicationState> stateMap =
+  private final ListMultimap<String,ReplicationState> stateMap =
       LinkedListMultimap.create();
   private final int maxLockRetries;
   private int lockRetryCount;
@@ -243,7 +243,7 @@
     stateMap.put(ref, state);
   }
 
-  Multimap<String,ReplicationState> getStates() {
+  ListMultimap<String,ReplicationState> getStates() {
     return stateMap;
   }
 
@@ -258,7 +258,7 @@
     return states.toArray(new ReplicationState[states.size()]);
   }
 
-  void addStates(Multimap<String,ReplicationState> states) {
+  void addStates(ListMultimap<String, ReplicationState> states) {
     stateMap.putAll(states);
   }
 
@@ -286,7 +286,7 @@
         }
       }).call();
     } catch (Exception e) {
-      Throwables.propagateIfPossible(e);
+      Throwables.throwIfUnchecked(e);
       throw new RuntimeException(e);
     } finally {
       statesCleanUp();
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 6717660..8df1c1c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java
@@ -55,7 +55,7 @@
     // Default doing nothing
   }
 
-  private static String resolveNodeName(URIish uri) {
+  static String resolveNodeName(URIish uri) {
     StringBuilder sb = new StringBuilder();
     if (uri.isRemote()) {
       sb.append(uri.getHost());
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 4b976bc..39bce3d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
@@ -13,18 +13,13 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.replication;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.PluginUser;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupIncludeCache;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -41,7 +36,9 @@
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 
 @Singleton
 public class ReplicationFileBasedConfig implements ReplicationConfig {
@@ -50,35 +47,15 @@
   private Path cfgPath;
   private boolean replicateAllOnPluginStart;
   private boolean defaultForceUpdate;
-  private Injector injector;
-  private final RemoteSiteUser.Factory replicationUserFactory;
-  private final PluginUser pluginUser;
-  private final GitRepositoryManager gitRepositoryManager;
-  private final GroupBackend groupBackend;
   private final FileBasedConfig config;
-  private final ReplicationStateListener stateLog;
-  private final GroupIncludeCache groupIncludeCache;
 
   @Inject
-  public ReplicationFileBasedConfig(Injector injector,
-      SitePaths site,
-      RemoteSiteUser.Factory ruf,
-      PluginUser pu,
-      GitRepositoryManager grm,
-      GroupBackend gb,
-      ReplicationStateListener stateLog,
-      GroupIncludeCache groupIncludeCache)
+  public ReplicationFileBasedConfig(SitePaths site,
+      DestinationFactory destinationFactory)
       throws ConfigInvalidException, IOException {
     this.cfgPath = site.etc_dir.resolve("replication.config");
-    this.groupIncludeCache = groupIncludeCache;
-    this.injector = injector;
-    this.replicationUserFactory = ruf;
-    this.pluginUser = pu;
-    this.gitRepositoryManager = grm;
-    this.groupBackend = gb;
     this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED);
-    this.destinations = allDestinations();
-    this.stateLog = stateLog;
+    this.destinations = allDestinations(destinationFactory);
   }
 
   /*
@@ -89,40 +66,26 @@
    */
   @Override
   public List<Destination> getDestinations(FilterType filterType) {
-    Predicate<Destination> filter;
+    Predicate<? super Destination> filter;
     switch (filterType) {
-      case PROJECT_CREATION :
-        filter = new Predicate<Destination>() {
-
-          @Override
-          public boolean apply(Destination dest) {
-            if (dest == null || !dest.isCreateMissingRepos()) {
-              return false;
-            }
-            return true;
-          }
-        };
+      case PROJECT_CREATION:
+        filter = dest -> dest.isCreateMissingRepos();
         break;
-      case PROJECT_DELETION :
-        filter = new Predicate<Destination>() {
-
-          @Override
-          public boolean apply(Destination dest) {
-            if (dest == null || !dest.isReplicateProjectDeletions()) {
-              return false;
-            }
-            return true;
-          }
-        };
+      case PROJECT_DELETION:
+        filter = dest -> dest.isReplicateProjectDeletions();
         break;
-      case ALL :
-        return destinations;
-      default :
-        return destinations;
+      case ALL:
+      default:
+        filter = dest -> true;
+        break;
     }
-    return FluentIterable.from(destinations).filter(filter).toList();
+    return destinations.stream()
+        .filter(Objects::nonNull)
+        .filter(filter)
+        .collect(toList());
   }
-  private List<Destination> allDestinations()
+
+  private List<Destination> allDestinations(DestinationFactory destinationFactory)
       throws ConfigInvalidException, IOException {
     if (!config.getFile().exists()) {
       log.warn("Config file " + config.getFile() + " does not exist; not replicating");
@@ -167,10 +130,8 @@
             .setForceUpdate(defaultForceUpdate));
       }
 
-      Destination destination =
-          new Destination(injector, new DestinationConfiguration(c,
-              config), replicationUserFactory, pluginUser,
-              gitRepositoryManager, groupBackend, stateLog, groupIncludeCache);
+      Destination destination = destinationFactory.create(
+          new DestinationConfiguration(c, config));
 
       if (!destination.isSingleProjectMatch()) {
         for (URIish u : c.getURIs()) {
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 5a5f3b4..c8957e7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
@@ -35,6 +35,7 @@
 class ReplicationModule extends AbstractModule {
   @Override
   protected void configure() {
+    bind(DestinationFactory.class).in(Scopes.SINGLETON);
     bind(ReplicationQueue.class).in(Scopes.SINGLETON);
 
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
@@ -66,6 +67,8 @@
 
     EventTypes.register(RefReplicatedEvent.TYPE, RefReplicatedEvent.class);
     EventTypes.register(RefReplicationDoneEvent.TYPE, RefReplicationDoneEvent.class);
+    EventTypes.register(
+        ReplicationScheduledEvent.TYPE, ReplicationScheduledEvent.class);
     bind(SshSessionFactory.class).toProvider(
         ReplicationSshSessionFactoryProvider.class);
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java
new file mode 100644
index 0000000..d24fbb5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2016 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.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.server.events.RefEvent;
+
+public class ReplicationScheduledEvent extends RefEvent {
+  static final String TYPE = "ref-replication-scheduled";
+
+  final String project;
+  final String ref;
+  final String targetNode;
+
+  public ReplicationScheduledEvent(String project, String ref,
+      String targetNode) {
+    super(TYPE);
+    this.project = project;
+    this.ref = ref;
+    this.targetNode = targetNode;
+  }
+
+  @Override
+  public String getRefName() {
+    return ref;
+  }
+
+  @Override
+  public NameKey getProjectNameKey() {
+    return new Project.NameKey(project);
+  }
+}
diff --git a/src/main/resources/Documentation/cmd-list.md b/src/main/resources/Documentation/cmd-list.md
index 3c6b78f..b6688e0 100644
--- a/src/main/resources/Documentation/cmd-list.md
+++ b/src/main/resources/Documentation/cmd-list.md
@@ -70,5 +70,5 @@
 SEE ALSO
 --------
 
-* [Replication Configuration](config.html)
+* [Replication Configuration](config.md)
 * [Access Control](../../../Documentation/access-control.html)
diff --git a/src/main/resources/Documentation/cmd-start.md b/src/main/resources/Documentation/cmd-start.md
index 19b33ec..59c3d1d 100644
--- a/src/main/resources/Documentation/cmd-start.md
+++ b/src/main/resources/Documentation/cmd-start.md
@@ -134,5 +134,5 @@
 SEE ALSO
 --------
 
-* [Replication Configuration](config.html)
+* [Replication Configuration](config.md)
 * [Access Control](../../../Documentation/access-control.html)
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 709d61f..cfdd91d 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -4,7 +4,7 @@
 Enabling Replication
 --------------------
 
-If replicating over SSH (recommended), ensure the host key of the
+If replicating over SSH, ensure the host key of the
 remote system(s) is already in the Gerrit user's `~/.ssh/known_hosts`
 file.  The easiest way to add the host key is to connect once by hand
 with the command line:
@@ -39,7 +39,7 @@
 ```
 
 To manually trigger replication at runtime, see
-SSH command [start](cmd-start.html).
+SSH command [start](cmd-start.md).
 
 File `replication.config`
 -------------------------
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 4cbac3a..71b191b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
@@ -32,15 +32,15 @@
 import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing;
 import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.net.URISyntaxException;
 
 @SuppressWarnings("unchecked")
-public class GitUpdateProcessingTest extends TestCase {
+public class GitUpdateProcessingTest {
   static {
     KeyUtil.setEncoderImpl(new StandardKeyEncoder());
   }
@@ -48,9 +48,8 @@
   private EventDispatcher dispatcherMock;
   private GitUpdateProcessing gitUpdateProcessing;
 
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
+  @Before
+  public void setUp() throws Exception {
     dispatcherMock = createMock(EventDispatcher.class);
     replay(dispatcherMock);
     ReviewDb reviewDbMock = createNiceMock(ReviewDb.class);
@@ -61,7 +60,8 @@
     gitUpdateProcessing = new GitUpdateProcessing(dispatcherMock);
   }
 
-  public void testHeadRefReplicated() throws URISyntaxException, OrmException {
+  @Test
+  public void headRefReplicated() throws URISyntaxException, OrmException {
     reset(dispatcherMock);
     RefReplicatedEvent expectedEvent =
         new RefReplicatedEvent("someProject", "refs/heads/master", "someHost",
@@ -76,7 +76,8 @@
     verify(dispatcherMock);
   }
 
-  public void testChangeRefReplicated() throws URISyntaxException, OrmException {
+  @Test
+  public void changeRefReplicated() throws URISyntaxException, OrmException {
     reset(dispatcherMock);
     RefReplicatedEvent expectedEvent =
         new RefReplicatedEvent("someProject", "refs/changes/01/1/1", "someHost",
@@ -91,7 +92,8 @@
     verify(dispatcherMock);
   }
 
-  public void testOnAllNodesReplicated() throws OrmException {
+  @Test
+  public void onAllNodesReplicated() throws OrmException {
     reset(dispatcherMock);
     RefReplicationDoneEvent expectedDoneEvent =
         new RefReplicationDoneEvent("someProject", "refs/heads/master", 5);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java
index 71b6600..144b116 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java
@@ -38,7 +38,7 @@
   }
 
   @Test
-  public void testUrlEncoding() {
+  public void urlEncoding() {
     assertThat(encode("foo/bar/thing")).isEqualTo("foo/bar/thing");
     assertThat(encode("-- All Projects --")).isEqualTo("--%20All%20Projects%20--");
     assertThat(encode("name/with a space")).isEqualTo("name/with%20a%20space");