Merge branch 'stable-3.2'

* stable-3.2:
  ReplicationConfig: Add missing JavaDoc
  Add method to push changes directly to given replica

Change-Id: I3d519d32d25cafc36a89785fe5b06f1ab3f1b00c
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
index 99e5ee4..8bbb180 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
@@ -67,8 +67,18 @@
    */
   Path getEventsDirectory();
 
+  /**
+   * Timeout for establishing SSH connection to remote.
+   *
+   * @return connection timeout, zero if infinite.
+   */
   int getSshConnectionTimeout();
 
+  /**
+   * Timeout for executing an SSH command on remote.
+   *
+   * @return command timeout, zero if infinite.
+   */
   int getSshCommandTimeout();
 
   /**
@@ -79,5 +89,10 @@
    */
   String getVersion();
 
+  /**
+   * Return a copy of the current config.
+   *
+   * @return the config.
+   */
   Config getConfig();
 }
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 a8ffeec..21e4227 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -19,6 +19,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Queues;
+import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.HeadUpdatedListener;
@@ -149,17 +150,40 @@
     }
 
     for (Destination cfg : destinations.get().getAll(FilterType.ALL)) {
-      if (cfg.wouldPushProject(project) && cfg.wouldPushRef(refName)) {
-        for (URIish uri : cfg.getURIs(project, urlMatch)) {
-          if (!isPersisted) {
-            replicationTasksStorage.create(
-                new ReplicateRefUpdate(project.get(), refName, uri, cfg.getRemoteConfigName()));
-          }
-          cfg.schedule(project, refName, uri, state, now);
+      pushReference(cfg, project, urlMatch, refName, state, now, isPersisted);
+    }
+  }
+
+  @UsedAt(UsedAt.Project.COLLABNET)
+  public void pushReference(Destination cfg, Project.NameKey project, String refName) {
+    pushReference(cfg, project, null, refName, null, true, false);
+  }
+
+  private void pushReference(
+      Destination cfg,
+      Project.NameKey project,
+      String urlMatch,
+      String refName,
+      ReplicationState state,
+      boolean now,
+      boolean isPersisted) {
+    boolean withoutState = state == null;
+    if (withoutState) {
+      state = new ReplicationState(new GitUpdateProcessing(dispatcher.get()));
+    }
+    if (cfg.wouldPushProject(project) && cfg.wouldPushRef(refName)) {
+      for (URIish uri : cfg.getURIs(project, urlMatch)) {
+        if (!isPersisted) {
+          replicationTasksStorage.create(
+              new ReplicateRefUpdate(project.get(), refName, uri, cfg.getRemoteConfigName()));
         }
-      } else {
-        repLog.atFine().log("Skipping ref %s on project %s", refName, project.get());
+        cfg.schedule(project, refName, uri, state, now);
       }
+    } else {
+      repLog.atFine().log("Skipping ref %s on project %s", refName, project.get());
+    }
+    if (withoutState) {
+      state.markAllPushTasksScheduled();
     }
   }