Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  PushOne: Improve format of log of references to be pushed
  Don't lose state when there's a pending push to the same ref

Change-Id: I78a3f0ff2e596a88d162fbe84caa1282ca1589f6
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 b206dbe..d9431f4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -429,7 +429,7 @@
         ScheduledFuture<?> ignored =
             pool.schedule(task, now ? 0 : config.getDelay(), TimeUnit.SECONDS);
         pending.put(uri, task);
-      } else if (!task.getRefs().contains(ref)) {
+      } else {
         addRef(task, ref);
         task.addState(ref, state);
       }
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 b64b024..b488264 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -16,8 +16,10 @@
 
 import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toMap;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Throwables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
@@ -62,6 +64,7 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.CredentialsProvider;
@@ -231,8 +234,7 @@
       delta.clear();
       pushAllRefs = true;
       repLog.trace("Added all refs for replication to {}", uri);
-    } else if (!pushAllRefs) {
-      delta.add(ref);
+    } else if (!pushAllRefs && delta.add(ref)) {
       repLog.trace("Added ref {} for replication to {}", ref, uri);
     }
   }
@@ -468,19 +470,46 @@
     }
 
     if (replConfig.getMaxRefsToLog() == 0 || todo.size() <= replConfig.getMaxRefsToLog()) {
-      repLog.info("Push to {} references: {}", uri, todo);
+      repLog.info("Push to {} references: {}", uri, refUpdatesForLogging(todo));
     } else {
       repLog.info(
           "Push to {} references (first {} of {} listed): {}",
           uri,
           replConfig.getMaxRefsToLog(),
           todo.size(),
-          todo.subList(0, replConfig.getMaxRefsToLog()));
+          refUpdatesForLogging(todo.subList(0, replConfig.getMaxRefsToLog())));
     }
 
     return tn.push(NullProgressMonitor.INSTANCE, todo);
   }
 
+  private static String refUpdatesForLogging(List<RemoteRefUpdate> refUpdates) {
+    return refUpdates.stream().map(PushOne::refUpdateForLogging).collect(joining(", "));
+  }
+
+  private static String refUpdateForLogging(RemoteRefUpdate update) {
+    String refSpec = String.format("%s:%s", update.getSrcRef(), update.getRemoteName());
+    String id =
+        String.format(
+            "%s..%s", objectIdToString(update.getExpectedOldObjectId()), update.getNewObjectId());
+    return MoreObjects.toStringHelper(RemoteRefUpdate.class)
+        .add("refSpec", refSpec)
+        .add("status", update.getStatus())
+        .add("id", id)
+        .add("force", booleanToString(update.isForceUpdate()))
+        .add("delete", booleanToString(update.isDelete()))
+        .add("ffwd", booleanToString(update.isFastForward()))
+        .toString();
+  }
+
+  private static String objectIdToString(ObjectId id) {
+    return id != null ? id.getName() : "(null)";
+  }
+
+  private static String booleanToString(boolean b) {
+    return b ? "yes" : "no";
+  }
+
   private List<RemoteRefUpdate> generateUpdates(Transport tn)
       throws IOException, PermissionBackendException {
     ProjectState projectState = projectCache.checkedGet(projectName);