Merge branch 'stable-2.11'

* stable-2.11:
  Emit replication status events after initial full sync
  Fix creation of missing repositories

Change-Id: I6a3dedcca8af777c2080c3bac9a4d0f80f0bb6f5
diff --git a/BUCK b/BUCK
index 2e8a623..8a84334 100644
--- a/BUCK
+++ b/BUCK
@@ -18,6 +18,7 @@
 java_test(
   name = 'replication_tests',
   srcs = glob(['src/test/java/**/*.java']),
+  labels = ['replication'],
   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..6b1687c 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;
@@ -57,10 +58,14 @@
     this.gitRepositoryManager = grm;
     this.groupBackend = gb;
     this.currentConfig = loadConfig();
-    this.currentConfigTs = currentConfig.getCfgPath().lastModified();
+    this.currentConfigTs = getLastModified(currentConfig);
     this.workQueue = workQueue;
   }
 
+  private static long getLastModified(ReplicationFileBasedConfig cfg) {
+    return FileUtil.lastModified(cfg.getCfgPath());
+  }
+
   private ReplicationFileBasedConfig loadConfig()
       throws ConfigInvalidException, IOException {
     return new ReplicationFileBasedConfig(injector, site,
@@ -79,25 +84,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/PushOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
index 0614959..32ffcbd 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.Stopwatch;
 import com.google.common.base.Throwables;
 import com.google.common.collect.LinkedListMultimap;
@@ -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;
@@ -79,8 +80,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 =
+  private static final ReplicationStateListener stateLog =
       new ReplicationStateLogger(repLog);
   static final String ALL_REFS = "..all..";
   static final String ID_MDC_KEY = "pushOneId";
@@ -611,7 +611,7 @@
     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/ReplicationFileBasedConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
index f56185d..c360f75 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;
@@ -61,13 +61,13 @@
       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");
+    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();
   }
 
@@ -215,7 +215,7 @@
     return destinations.isEmpty();
   }
 
-  File getCfgPath() {
+  Path getCfgPath() {
     return cfgPath;
   }
 
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..ea5be66 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -63,7 +63,7 @@
   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 =
+  private static final ReplicationStateListener stateLog =
       new ReplicationStateLogger(repLog);
 
   static String replaceName(String in, String name, boolean keyIsOptional) {
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..a1bc5a4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java
@@ -24,7 +24,7 @@
  * and logs additional information about the replication state to the
  * stderr console.
  */
-public class ReplicationStateLogger {
+public class ReplicationStateLogger implements ReplicationStateListener {
 
   private final Logger logger;
 
@@ -32,21 +32,19 @@
     this.logger = logger;
   }
 
+  @Override
   public void warn(String msg, ReplicationState... states) {
     stateWriteErr("Warning: " + msg, states);
     logger.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);
   }
 
+  @Override
   public void error(String msg, Throwable t, ReplicationState... states) {
     stateWriteErr("Error: " + msg, states);
     logger.error(msg, t);
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();