Provide an option to skip replication of NoteDb meta refs

Replicating NoteDb meta refs is not needed when the remote does not run
a Gerrit instance.

Provide a config `remote.<NAME>.replicateNoteDbMetaRefs` which implies
whether to replicate the NoteDb meta refs (`refs/changes/*/meta`,
`refs/changes/*/robot-comments`, `refs/draft-comments/*`,
`refs/starred-changes/*`) or not. Time taken to replicate to a
non-Gerrit replica can be improved with this change.

Change-Id: I653f2e0a5b66dcded40c11ca23d2d22c63ccc396
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 a7235d5..04af9db 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -803,6 +803,10 @@
     return config.getPushBatchSize();
   }
 
+  boolean replicateNoteDbMetaRefs() {
+    return config.replicateNoteDbMetaRefs();
+  }
+
   private static boolean matches(URIish uri, String urlMatch) {
     if (urlMatch == null || urlMatch.equals("") || urlMatch.equals("*")) {
       return true;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
index a74d198..4d72ff0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
@@ -39,6 +39,7 @@
   private final ImmutableList<String> adminUrls;
   private final int poolThreads;
   private final boolean createMissingRepos;
+  private final boolean replicateNoteDbMetaRefs;
   private final boolean replicatePermissions;
   private final boolean replicateProjectDeletions;
   private final boolean replicateHiddenProjects;
@@ -71,6 +72,7 @@
             "updateRefErrorMaxRetries",
             cfg.getInt("replication", "lockErrorMaxRetries", 0));
     createMissingRepos = cfg.getBoolean("remote", name, "createMissingRepositories", true);
+    replicateNoteDbMetaRefs = cfg.getBoolean("remote", name, "replicateNoteDbMetaRefs", true);
     replicatePermissions = cfg.getBoolean("remote", name, "replicatePermissions", true);
     replicateProjectDeletions = cfg.getBoolean("remote", name, "replicateProjectDeletions", false);
     replicateHiddenProjects = cfg.getBoolean("remote", name, "replicateHiddenProjects", false);
@@ -177,6 +179,11 @@
     return createMissingRepos;
   }
 
+  @Override
+  public boolean replicateNoteDbMetaRefs() {
+    return replicateNoteDbMetaRefs;
+  }
+
   public boolean replicateProjectDeletions() {
     return replicateProjectDeletions;
   }
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 c36b42c..ed84087 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -750,7 +750,8 @@
 
   private boolean canPushRef(String ref, boolean noPerms) {
     return !(noPerms && RefNames.REFS_CONFIG.equals(ref))
-        && !ref.startsWith(RefNames.REFS_CACHE_AUTOMERGE);
+        && !ref.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
+        && !(!pool.replicateNoteDbMetaRefs() && RefNames.isNoteDbMetaRef(ref));
   }
 
   private Map<String, Ref> listRemote(Transport tn)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
index 5fe0323..05b4066 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
@@ -105,6 +105,13 @@
   int getPushBatchSize();
 
   /**
+   * Whether to replicate the NoteDb meta refs or not.
+   *
+   * @return boolean, true by default
+   */
+  boolean replicateNoteDbMetaRefs();
+
+  /**
    * Whether the remote configuration is for a single project only
    *
    * @return true, when configuration is for a single project, false otherwise
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 76de9e8..8f1ca86 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -587,6 +587,16 @@
 	Note that `pushBatchSize` is ignored when *Cluster Replication* is configured
 	- when `replication.distributionInterval` has value > 0.
 
+remote.NAME.replicateNoteDbMetaRefs
+: Whether to replicate the NoteDb meta refs (`refs/changes/*/meta`,
+  `refs/changes/*/robot-comments`, `refs/draft-comments/*`,
+  `refs/starred-changes/*`) or not. This setting is useful when the remote
+  replica does not run a Gerrit instance and one wants to turn off replicating
+  NoteDb meta refs to that remote.
+
+  By default, true.
+
+
 Directory `replication`
 --------------------
 The optional directory `$site_path/etc/replication` contains Git-style
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
index fd08b7b..8664adc 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
@@ -234,6 +234,7 @@
 
   @Test
   public void shouldPushMetaRefTogetherWithChangeRef() throws InterruptedException, IOException {
+    when(destinationMock.replicateNoteDbMetaRefs()).thenReturn(true);
     PushOne pushOne = Mockito.spy(createPushOne(null));
 
     Ref newLocalChangeRef =
@@ -261,6 +262,36 @@
   }
 
   @Test
+  public void skipPushingMetaRefWhenReplicateNoteDbMetaRefsIsSetToFalse()
+      throws InterruptedException, IOException {
+    when(destinationMock.replicateNoteDbMetaRefs()).thenReturn(false);
+    PushOne pushOne = Mockito.spy(createPushOne(null));
+
+    Ref newLocalChangeRef =
+        new ObjectIdRef.Unpeeled(
+            NEW,
+            "refs/changes/11/11111/1",
+            ObjectId.fromString("0000000000000000000000000000000000000002"));
+
+    Ref newLocalChangeMetaRef =
+        new ObjectIdRef.Unpeeled(
+            NEW,
+            "refs/changes/11/11111/meta",
+            ObjectId.fromString("0000000000000000000000000000000000000003"));
+
+    localRefs.add(newLocalChangeRef);
+    localRefs.add(newLocalChangeMetaRef);
+
+    pushOne.addRefBatch(
+        ImmutableSet.of(newLocalChangeRef.getName(), newLocalChangeMetaRef.getName()));
+    pushOne.run();
+
+    isCallFinished.await(10, TimeUnit.SECONDS);
+    verify(transportMock, atLeastOnce()).push(any(), any());
+    verify(pushOne, times(1)).push(any(), any(), any());
+  }
+
+  @Test
   public void shouldNotAttemptDuplicateRemoteRefUpdate() throws InterruptedException, IOException {
     PushOne pushOne = Mockito.spy(createPushOne(null));