Merge branch 'stable-2.14'

* stable-2.14:
  Fix replication retries when maxRetries is set to 0

Change-Id: I3d2986f1707bf1b60675ea28fe3dbcc8dfb04681
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 603a70f..f7dd30a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -24,8 +24,10 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.EventDispatcher;
 import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
@@ -41,6 +43,9 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.PerThreadRequestScope;
 import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.PerRequestProjectControlCache;
 import com.google.gerrit.server.project.ProjectControl;
@@ -76,6 +81,7 @@
   private final PushOne.Factory opFactory;
   private final ProjectControl.Factory projectControlFactory;
   private final GitRepositoryManager gitManager;
+  private final PermissionBackend permissionBackend;
   private volatile WorkQueue.Executor pool;
   private final PerThreadRequestScope.Scoper threadScoper;
   private final DestinationConfiguration config;
@@ -103,6 +109,7 @@
       RemoteSiteUser.Factory replicationUserFactory,
       PluginUser pluginUser,
       GitRepositoryManager gitRepositoryManager,
+      PermissionBackend permissionBackend,
       GroupBackend groupBackend,
       ReplicationStateListener stateLog,
       GroupIncludeCache groupIncludeCache,
@@ -110,6 +117,7 @@
     config = cfg;
     this.eventDispatcher = eventDispatcher;
     gitManager = gitRepositoryManager;
+    this.permissionBackend = permissionBackend;
     this.stateLog = stateLog;
 
     final CurrentUser remoteUser;
@@ -210,9 +218,19 @@
     return cnt;
   }
 
-  private boolean shouldReplicate(ProjectControl projectControl) {
-    return projectControl.isReadable()
-        && (!projectControl.isHidden() || config.replicateHiddenProjects());
+  private boolean shouldReplicate(ProjectControl ctl) throws PermissionBackendException {
+    if (!config.replicateHiddenProjects() && ctl.getProject().getState() == ProjectState.HIDDEN) {
+      return false;
+    }
+    try {
+      permissionBackend
+          .user(ctl.getUser())
+          .project(ctl.getProject().getNameKey())
+          .check(ProjectPermission.ACCESS);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 
   private boolean shouldReplicate(
@@ -222,7 +240,7 @@
           .scope(
               new Callable<Boolean>() {
                 @Override
-                public Boolean call() throws NoSuchProjectException {
+                public Boolean call() throws NoSuchProjectException, PermissionBackendException {
                   ProjectControl projectControl = controlFor(project);
                   return shouldReplicate(projectControl)
                       && (PushOne.ALL_REFS.equals(ref)
@@ -245,7 +263,7 @@
           .scope(
               new Callable<Boolean>() {
                 @Override
-                public Boolean call() throws NoSuchProjectException {
+                public Boolean call() throws NoSuchProjectException, PermissionBackendException {
                   return shouldReplicate(controlFor(project));
                 }
               })
@@ -260,6 +278,11 @@
   }
 
   void schedule(Project.NameKey project, String ref, URIish uri, ReplicationState state) {
+    schedule(project, ref, uri, state, false);
+  }
+
+  void schedule(
+      Project.NameKey project, String ref, URIish uri, ReplicationState state, boolean now) {
     repLog.info("scheduling replication {}:{} => {}", project, ref, uri);
     if (!shouldReplicate(project, ref, state)) {
       return;
@@ -295,7 +318,7 @@
       if (e == null) {
         e = opFactory.create(project, uri);
         addRef(e, ref);
-        pool.schedule(e, config.getDelay(), TimeUnit.SECONDS);
+        pool.schedule(e, now ? 0 : config.getDelay(), TimeUnit.SECONDS);
         pending.put(uri, e);
       } else if (!e.getRefs().contains(ref)) {
         addRef(e, ref);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java
index df886cb..83eab86 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupIncludeCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
@@ -30,6 +31,7 @@
   private final RemoteSiteUser.Factory replicationUserFactory;
   private final PluginUser pluginUser;
   private final GitRepositoryManager gitRepositoryManager;
+  private final PermissionBackend permissionBackend;
   private final GroupBackend groupBackend;
   private final ReplicationStateListener stateLog;
   private final GroupIncludeCache groupIncludeCache;
@@ -41,6 +43,7 @@
       RemoteSiteUser.Factory replicationUserFactory,
       PluginUser pluginUser,
       GitRepositoryManager gitRepositoryManager,
+      PermissionBackend permissionBackend,
       GroupBackend groupBackend,
       ReplicationStateListener stateLog,
       GroupIncludeCache groupIncludeCache,
@@ -49,6 +52,7 @@
     this.replicationUserFactory = replicationUserFactory;
     this.pluginUser = pluginUser;
     this.gitRepositoryManager = gitRepositoryManager;
+    this.permissionBackend = permissionBackend;
     this.groupBackend = groupBackend;
     this.stateLog = stateLog;
     this.groupIncludeCache = groupIncludeCache;
@@ -62,6 +66,7 @@
         replicationUserFactory,
         pluginUser,
         gitRepositoryManager,
+        permissionBackend,
         groupBackend,
         stateLog,
         groupIncludeCache,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
index a6b38c1..227804d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
@@ -56,7 +56,9 @@
         && config.isReplicateAllOnPluginStart()) {
       ReplicationState state = new ReplicationState(new GitUpdateProcessing(eventDispatcher.get()));
       pushAllFuture.set(
-          pushAll.create(null, ReplicationFilter.all(), state).schedule(30, TimeUnit.SECONDS));
+          pushAll
+              .create(null, ReplicationFilter.all(), state, false)
+              .schedule(30, TimeUnit.SECONDS));
     }
   }
 
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 da32ecd..db067e2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
@@ -27,7 +27,7 @@
   private final ReplicationStateListener stateLog;
 
   public interface Factory {
-    PushAll create(String urlMatch, ReplicationFilter filter, ReplicationState state);
+    PushAll create(String urlMatch, ReplicationFilter filter, ReplicationState state, boolean now);
   }
 
   private final WorkQueue workQueue;
@@ -36,6 +36,7 @@
   private final String urlMatch;
   private final ReplicationFilter filter;
   private final ReplicationState state;
+  private final boolean now;
 
   @Inject
   protected PushAll(
@@ -45,7 +46,8 @@
       ReplicationStateListener stateLog,
       @Assisted @Nullable String urlMatch,
       @Assisted ReplicationFilter filter,
-      @Assisted ReplicationState state) {
+      @Assisted ReplicationState state,
+      @Assisted boolean now) {
     this.workQueue = wq;
     this.projectCache = projectCache;
     this.replication = rq;
@@ -53,6 +55,7 @@
     this.urlMatch = urlMatch;
     this.filter = filter;
     this.state = state;
+    this.now = now;
   }
 
   Future<?> schedule(long delay, TimeUnit unit) {
@@ -64,7 +67,7 @@
     try {
       for (Project.NameKey nameKey : projectCache.all()) {
         if (filter.matches(nameKey)) {
-          replication.scheduleFullSync(nameKey, urlMatch, state);
+          replication.scheduleFullSync(nameKey, urlMatch, state, now);
         }
       }
     } catch (Exception e) {
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 525c990..e472954 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -35,6 +36,9 @@
 import com.google.gerrit.server.git.VisibleRefFilter;
 import com.google.gerrit.server.git.WorkQueue.CanceledWhileRunning;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.util.IdGenerator;
@@ -90,6 +94,7 @@
   }
 
   private final GitRepositoryManager gitManager;
+  private final PermissionBackend permissionBackend;
   private final SchemaFactory<ReviewDb> schema;
   private final Destination pool;
   private final RemoteConfig config;
@@ -120,6 +125,7 @@
   @Inject
   PushOne(
       GitRepositoryManager grm,
+      PermissionBackend permissionBackend,
       SchemaFactory<ReviewDb> s,
       Destination p,
       RemoteConfig c,
@@ -135,6 +141,7 @@
       @Assisted Project.NameKey d,
       @Assisted URIish u) {
     gitManager = grm;
+    this.permissionBackend = permissionBackend;
     schema = s;
     pool = p;
     config = c;
@@ -387,7 +394,7 @@
       }
     } catch (IOException e) {
       stateLog.error("Cannot replicate to " + uri, e, getStatesAsArray());
-    } catch (RuntimeException | Error e) {
+    } catch (PermissionBackendException | RuntimeException | Error e) {
       stateLog.error("Unexpected error during replication to " + uri, e, getStatesAsArray());
     } finally {
       if (git != null) {
@@ -426,7 +433,7 @@
     }
   }
 
-  private void runImpl() throws IOException {
+  private void runImpl() throws IOException, PermissionBackendException {
     PushResult res;
     try (Transport tn = Transport.open(git, uri)) {
       res = pushVia(tn);
@@ -435,7 +442,7 @@
   }
 
   private PushResult pushVia(Transport tn)
-      throws IOException, NotSupportedException, TransportException {
+      throws IOException, NotSupportedException, TransportException, PermissionBackendException {
     tn.applyConfig(config);
     tn.setCredentialsProvider(credentialsProvider);
 
@@ -453,7 +460,8 @@
     return tn.push(NullProgressMonitor.INSTANCE, todo);
   }
 
-  private List<RemoteRefUpdate> generateUpdates(Transport tn) throws IOException {
+  private List<RemoteRefUpdate> generateUpdates(Transport tn)
+      throws IOException, PermissionBackendException {
     ProjectControl pc;
     try {
       pc = pool.controlFor(projectName);
@@ -462,7 +470,14 @@
     }
 
     Map<String, Ref> local = git.getAllRefs();
-    if (!pc.allRefsAreVisible()) {
+    boolean filter;
+    try {
+      permissionBackend.user(pc.getUser()).project(projectName).check(ProjectPermission.READ);
+      filter = false;
+    } catch (AuthException e) {
+      filter = true;
+    }
+    if (filter) {
       if (!pushAllRefs) {
         // If we aren't mirroring, reduce the space we need to filter
         // to only the references we will update during this operation.
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 9a68d32..bff4651 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -109,8 +109,12 @@
     }
   }
 
+  void scheduleFullSync(Project.NameKey project, String urlMatch, ReplicationState state) {
+    scheduleFullSync(project, urlMatch, state, false);
+  }
+
   void scheduleFullSync(
-      final Project.NameKey project, final String urlMatch, ReplicationState state) {
+      Project.NameKey project, String urlMatch, ReplicationState state, boolean now) {
     if (!running) {
       stateLog.warn("Replication plugin did not finish startup before event", state);
       return;
@@ -119,7 +123,7 @@
     for (Destination cfg : config.getDestinations(FilterType.ALL)) {
       if (cfg.wouldPushProject(project)) {
         for (URIish uri : cfg.getURIs(project, urlMatch)) {
-          cfg.schedule(project, PushOne.ALL_REFS, uri, state);
+          cfg.schedule(project, PushOne.ALL_REFS, uri, state, now);
         }
       }
     }
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 c701c21..c3f7eee 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
@@ -44,6 +44,9 @@
   @Option(name = "--wait", usage = "wait for replication to finish before exiting")
   private boolean wait;
 
+  @Option(name = "--now", usage = "start replication without waiting for replicationDelay")
+  private boolean now;
+
   @Argument(index = 0, multiValued = true, metaVar = "PATTERN", usage = "project name pattern")
   private List<String> projectPatterns = new ArrayList<>(2);
 
@@ -66,7 +69,7 @@
       projectFilter = new ReplicationFilter(projectPatterns);
     }
 
-    future = pushFactory.create(urlMatch, projectFilter, state).schedule(0, TimeUnit.SECONDS);
+    future = pushFactory.create(urlMatch, projectFilter, state, now).schedule(0, TimeUnit.SECONDS);
 
     if (wait) {
       if (future != null) {
diff --git a/src/main/resources/Documentation/cmd-start.md b/src/main/resources/Documentation/cmd-start.md
index 59c3d1d..6af73af 100644
--- a/src/main/resources/Documentation/cmd-start.md
+++ b/src/main/resources/Documentation/cmd-start.md
@@ -9,6 +9,7 @@
 --------
 ```
 ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ start
+  [--now]
   [--wait]
   [--url <PATTERN>]
   {--all | <PROJECT PATTERN> ...}
@@ -85,6 +86,10 @@
 OPTIONS
 -------
 
+`--now`
+:   Start replicating right away without waiting the per remote
+	replication delay.
+
 `--wait`
 :	Wait for replication to finish before exiting.