Merge branch 'stable-3.11' into stable-3.12

* stable-3.11:
  Fix pull replication filtering for deleted refs
  Revert "Do not filter out refs removals when filtering pull-replication fetches"

Change-Id: Ib85084ac3496992dc089215982f43ba4eed34e37
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
index 8423d2e..a620dce 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -86,6 +87,28 @@
                             ref,
                             oid.getName()));
 
+                try {
+                  Ref localRef = refDb.exactRef(ref);
+                  if (localRef == null
+                      && foundAsZeroInSharedRefDb(Project.nameKey(projectName), ref)) {
+                    repLog.info(
+                        "{}:{} was removed locally and is set to zeros in the shared-refdb and thus"
+                            + " will NOT BE fetched",
+                        projectName,
+                        ref);
+                    return false;
+                  }
+                } catch (IOException ioe) {
+                  String message =
+                      String.format(
+                          "Cannot dereference ref '%s' for project '%s' therefore will NOT BE"
+                              + " fetched",
+                          ref, projectName);
+                  repLog.error(message);
+                  logger.atSevere().withCause(ioe).log("%s", message);
+                  return false;
+                }
+
                 return !localRefOid.isPresent();
               })
           .collect(Collectors.toSet());
@@ -133,6 +156,31 @@
     return refLocks;
   }
 
+  /* If the ref to fetch has been set to all zeros on the global-refdb, it means
+   * that whatever is the situation locally, we do not need to fetch it:
+   * - If the remote still has it, fetching it will be useless because the global
+   *   state is that the ref should be removed.
+   * - If the remote doesn't have it anymore, trying to fetch the ref won't do
+   *   anything because you can't just remove local refs by fetching.
+   */
+  private boolean hasBeenRemovedFromGlobalRefDb(String projectName, String ref) {
+    if (foundAsZeroInSharedRefDb(Project.nameKey(projectName), ref)) {
+      repLog.info(
+          "{}:{} is found as zeros (removed) in shared-refdb thus will NOT BE fetched",
+          projectName,
+          ref);
+      return true;
+    }
+    return false;
+  }
+
+  private boolean foundAsZeroInSharedRefDb(NameKey projectName, String ref) {
+    return sharedRefDb
+        .get(projectName, ref, String.class)
+        .map(r -> ZERO_ID_NAME.equals(r))
+        .orElse(false);
+  }
+
   private Optional<ObjectId> getLocalSha1IfEqualsToExistingGlobalRefDb(
       Repository repository,
       String projectName,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilterTest.java
index b9ec588..2216544 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilterTest.java
@@ -201,6 +201,23 @@
   }
 
   @Test
+  public void shouldNotFilterOutWhenRefIsMissingOnlyInTheLocalRepository() throws Exception {
+    String refObjectId = "0000000000000000000000000000000000000001";
+    String nonExistingLocalRef = "refs/heads/temporaryOutdated";
+
+    Set<String> refsToFetch = Set.of(nonExistingLocalRef);
+    doReturn(Optional.of(refObjectId))
+        .when(sharedRefDatabaseMock)
+        .get(eq(projectName), any(), any());
+
+    MultisiteReplicationFetchFilter fetchFilter =
+        new MultisiteReplicationFetchFilter(sharedRefDatabaseMock, gitRepositoryManager, config);
+    Set<String> filteredRefsToFetch = fetchFilter.filter(project, refsToFetch);
+
+    assertThat(filteredRefsToFetch).hasSize(1);
+  }
+
+  @Test
   public void shouldNotFilterOutRefThatDoesntExistLocallyOrInSharedRefDb() throws Exception {
     String nonExisting = "refs/heads/non-existing-ref";
 
@@ -213,6 +230,23 @@
     assertThat(filteredRefsToFetch).hasSize(1);
   }
 
+  @Test
+  public void shouldFilterOutRefMissingInTheLocalRepositoryAndDeletedInSharedRefDb()
+      throws Exception {
+    String nonExistingLocalRef = "refs/heads/temporaryOutdated";
+
+    Set<String> refsToFetch = Set.of(nonExistingLocalRef);
+    doReturn(Optional.of(ObjectId.zeroId().getName()))
+        .when(sharedRefDatabaseMock)
+        .get(eq(projectName), any(), any());
+
+    MultisiteReplicationFetchFilter fetchFilter =
+        new MultisiteReplicationFetchFilter(sharedRefDatabaseMock, gitRepositoryManager, config);
+    Set<String> filteredRefsToFetch = fetchFilter.filter(project, refsToFetch);
+
+    assertThat(filteredRefsToFetch).hasSize(0);
+  }
+
   private RevCommit newRef(String refName) throws Exception {
     return repo.branch(refName).commit().create();
   }