Add remote.<name>.replicationdelay to control delay

This way administrators can set the delay for farther away remotes to
be longer, and for really close remotes to be much shorter.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/config-replication.txt b/Documentation/config-replication.txt
index 9eb6493..9195c56 100644
--- a/Documentation/config-replication.txt
+++ b/Documentation/config-replication.txt
@@ -6,9 +6,9 @@
 provide mirroring of changes, for warm-standby backups, or a
 load-balanced public mirror farm.
 
-The replication runs on a 15 second delay.  This gives Gerrit a short
-time window to batch updates going to the same project, such as when a
-user uploads multiple changes at once.
+The replication runs on a short delay.  This gives Gerrit a small
+time window to batch updates going to the same project, such as
+when a user uploads multiple changes at once.
 
 Typically replication should be done over SSH, with a passwordless
 public/private key pair.  On a trusted network it is also possible to
@@ -99,6 +99,16 @@
 +
 Defaults to `+refs/\*:refs/*` (all refs) if not specified.
 
+remote.<name>.replicationDelay::
++
+Number of seconds to wait before scheduling a remote push operation.
+Setting the delay to 0 effectively disables the delay, causing the
+push to start as soon as possible.
++
+This is a Gerrit specific extension to the Git remote block.
++
+By default, 15 seconds.
+
 
 File `~/.ssh/config`[[ssh_config]]
 ----------------------------------
diff --git a/src/main/java/com/google/gerrit/git/PushQueue.java b/src/main/java/com/google/gerrit/git/PushQueue.java
index a85ef5c..6426da2 100644
--- a/src/main/java/com/google/gerrit/git/PushQueue.java
+++ b/src/main/java/com/google/gerrit/git/PushQueue.java
@@ -45,8 +45,7 @@
 /** Manages automatic replication to remote repositories. */
 public class PushQueue {
   static final Logger log = LoggerFactory.getLogger(PushQueue.class);
-  private static final int startDelay = 15; // seconds
-  private static List<RemoteConfig> configs;
+  private static List<ReplicationConfig> configs;
   private static final Map<URIish, PushOp> pending =
       new HashMap<URIish, PushOp>();
 
@@ -78,9 +77,9 @@
    * @param project identity of the project to replicate.
    */
   public static void scheduleFullSync(final Project.NameKey project) {
-    for (final RemoteConfig cfg : allConfigs()) {
-      for (final URIish uri : cfg.getURIs()) {
-        scheduleImp(project, PushOp.MIRROR_ALL, cfg, expandURI(uri, project));
+    for (final ReplicationConfig cfg : allConfigs()) {
+      for (final URIish uri : cfg.getURIs(project)) {
+        scheduleImp(project, PushOp.MIRROR_ALL, cfg, uri);
       }
     }
   }
@@ -97,35 +96,21 @@
    */
   public static void scheduleUpdate(final Project.NameKey project,
       final String ref) {
-    for (final RemoteConfig cfg : allConfigs()) {
-      if (wouldPushRef(cfg, ref)) {
-        for (final URIish uri : cfg.getURIs()) {
-          scheduleImp(project, ref, cfg, expandURI(uri, project));
+    for (final ReplicationConfig cfg : allConfigs()) {
+      if (cfg.wouldPushRef(ref)) {
+        for (final URIish uri : cfg.getURIs(project)) {
+          scheduleImp(project, ref, cfg, uri);
         }
       }
     }
   }
 
-  private static boolean wouldPushRef(final RemoteConfig cfg, final String ref) {
-    for (final RefSpec s : cfg.getPushRefSpecs()) {
-      if (s.matchSource(ref)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private static URIish expandURI(URIish uri, final Project.NameKey project) {
-    uri = uri.setPath(replace(uri.getPath(), "name", project.get()));
-    return uri;
-  }
-
   private static synchronized void scheduleImp(final Project.NameKey project,
-      final String ref, final RemoteConfig srcConf, final URIish uri) {
+      final String ref, final ReplicationConfig config, final URIish uri) {
     PushOp e = pending.get(uri);
     if (e == null) {
-      e = new PushOp(project.get(), srcConf, uri);
-      WorkQueue.schedule(e, startDelay, TimeUnit.SECONDS);
+      e = new PushOp(project.get(), config.remote, uri);
+      WorkQueue.schedule(e, config.delay, TimeUnit.SECONDS);
       pending.put(uri, e);
     }
     e.addRef(ref);
@@ -141,7 +126,7 @@
     return pat.substring(0, n) + val + pat.substring(n + 3 + key.length());
   }
 
-  private static synchronized List<RemoteConfig> allConfigs() {
+  private static synchronized List<ReplicationConfig> allConfigs() {
     if (configs == null) {
       final File path;
       try {
@@ -160,7 +145,8 @@
       final RepositoryConfig cfg = new RepositoryConfig(null, cfgFile);
       try {
         cfg.load();
-        final ArrayList<RemoteConfig> r = new ArrayList<RemoteConfig>();
+
+        final List<ReplicationConfig> r = new ArrayList<ReplicationConfig>();
         for (final RemoteConfig c : RemoteConfig.getAllRemoteConfigs(cfg)) {
           if (c.getURIs().isEmpty()) {
             continue;
@@ -180,7 +166,7 @@
             c.addPushRefSpec(spec);
           }
 
-          r.add(c);
+          r.add(new ReplicationConfig(c, cfg));
         }
         configs = Collections.unmodifiableList(r);
       } catch (FileNotFoundException e) {
@@ -196,4 +182,37 @@
     }
     return configs;
   }
+
+  private static class ReplicationConfig {
+    final RemoteConfig remote;
+    final int delay;
+
+    ReplicationConfig(final RemoteConfig rc, final RepositoryConfig cfg) {
+      remote = rc;
+      delay = posInt(rc, cfg, "replicationdelay", 15);
+    }
+
+    private static int posInt(final RemoteConfig rc, final RepositoryConfig cfg,
+        final String name, final int defValue) {
+      return Math.max(0, cfg.getInt("remote", rc.getName(), name, defValue));
+    }
+
+    boolean wouldPushRef(final String ref) {
+      for (final RefSpec s : remote.getPushRefSpecs()) {
+        if (s.matchSource(ref)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    List<URIish> getURIs(final Project.NameKey project) {
+      final List<URIish> r = new ArrayList<URIish>(remote.getURIs().size());
+      for (URIish uri : remote.getURIs()) {
+        uri = uri.setPath(replace(uri.getPath(), "name", project.get()));
+        r.add(uri);
+      }
+      return r;
+    }
+  }
 }