Merge branch 'stable-3.1' into stable-3.2

* stable-3.1:
  Use new kafka broker name

Change-Id: I8efd24ffc461641d627f97cc8afbc2c7b4a27e83
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 9cbb332..fee8aca 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -9,6 +9,6 @@
 
     maven_jar(
         name = "events-broker",
-        artifact = "com.gerritforge:events-broker:3.1.4",
-        sha1 = "5672908dde0bd02cabc95efe34a8d8507d44b6ac",
+        artifact = "com.gerritforge:events-broker:3.2.0-rc4",
+        sha1 = "53e3f862ac2c2196dba716756ac9586f4b63af47",
     )
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index 87af305..f3f3ae6 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -16,7 +16,7 @@
 
 
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-GERRIT_BRANCH=stable-3.1
+GERRIT_BRANCH=stable-3.2
 GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job
 LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
 EVENTS_BROKER_VER=`grep 'com.gerritforge:events-broker' $(dirname $0)/../external_plugin_deps.bzl | cut -d '"' -f 2 | cut -d ':' -f 3`
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
index ebe28d4..f509cd4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -61,6 +61,7 @@
   private final Supplier<Cache> cache;
   private final Supplier<Event> event;
   private final Supplier<Index> index;
+  private final Supplier<Projects> projects;
   private final Supplier<SharedRefDatabase> sharedRefDb;
   private final Supplier<Collection<Message>> replicationConfigValidation;
   private final Supplier<Broker> broker;
@@ -79,6 +80,7 @@
     cache = memoize(() -> new Cache(lazyMultiSiteCfg));
     event = memoize(() -> new Event(lazyMultiSiteCfg));
     index = memoize(() -> new Index(lazyMultiSiteCfg));
+    projects = memoize(() -> new Projects(lazyMultiSiteCfg));
     sharedRefDb = memoize(() -> new SharedRefDatabase(lazyMultiSiteCfg));
     broker = memoize(() -> new Broker(lazyMultiSiteCfg));
   }
@@ -107,6 +109,10 @@
     return broker.get();
   }
 
+  public Projects projects() {
+    return projects.get();
+  }
+
   public Collection<Message> validate() {
     return replicationConfigValidation.get();
   }
@@ -198,6 +204,20 @@
     }
   }
 
+  public static class Projects {
+    public static final String SECTION = "projects";
+    public static final String PATTERN_KEY = "pattern";
+    public List<String> patterns;
+
+    public Projects(Supplier<Config> cfg) {
+      patterns = ImmutableList.copyOf(cfg.get().getStringList("projects", null, PATTERN_KEY));
+    }
+
+    public List<String> getPatterns() {
+      return patterns;
+    }
+  }
+
   /** Common parameters to cache, event, index */
   public abstract static class Forwarding {
     static final boolean DEFAULT_SYNCHRONIZE = true;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java
new file mode 100644
index 0000000..d6fac47
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilter.java
@@ -0,0 +1,109 @@
+// Copyright (C) 2020 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.multisite;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.ProjectEvent;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.List;
+import java.util.Set;
+
+@Singleton
+public class ProjectsFilter {
+  public enum PatternType {
+    REGEX,
+    WILDCARD,
+    EXACT_MATCH;
+
+    public static PatternType getPatternType(String pattern) {
+      if (pattern.startsWith(AccessSection.REGEX_PREFIX)) {
+        return REGEX;
+      } else if (pattern.endsWith("*")) {
+        return WILDCARD;
+      } else {
+        return EXACT_MATCH;
+      }
+    }
+  }
+
+  private Set<NameKey> globalProjects = Sets.newConcurrentHashSet();
+  private Set<NameKey> localProjects = Sets.newConcurrentHashSet();
+  private final List<String> projectPatterns;
+
+  @Inject
+  public ProjectsFilter(Configuration cfg) {
+    projectPatterns = cfg.projects().getPatterns();
+  }
+
+  public boolean matches(Event event) {
+    if (event == null) {
+      throw new IllegalArgumentException("Event object cannot be null");
+    }
+    if (event instanceof ProjectEvent) {
+      return matches(((ProjectEvent) event).getProjectNameKey());
+    }
+    return false;
+  }
+
+  public boolean matches(String projectName) {
+    return matches(NameKey.parse(projectName));
+  }
+
+  public boolean matches(Project.NameKey name) {
+    if (name == null || Strings.isNullOrEmpty(name.get())) {
+      throw new IllegalArgumentException(
+          String.format("Project name cannot be null or empty, but was %s", name));
+    }
+    if (projectPatterns.isEmpty() || globalProjects.contains(name)) {
+      return true;
+    }
+
+    if (localProjects.contains(name)) {
+      return false;
+    }
+
+    String projectName = name.get();
+
+    for (String pattern : projectPatterns) {
+      if (matchesPattern(projectName, pattern)) {
+        globalProjects.add(name);
+        return true;
+      }
+    }
+    localProjects.add(name);
+    return false;
+  }
+
+  private boolean matchesPattern(String projectName, String pattern) {
+    boolean match = false;
+    switch (PatternType.getPatternType(pattern)) {
+      case REGEX:
+        match = projectName.matches(pattern);
+        break;
+      case WILDCARD:
+        match = projectName.startsWith(pattern.substring(0, pattern.length() - 1));
+        break;
+      case EXACT_MATCH:
+        match = projectName.equals(pattern);
+    }
+    return match;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
index b56f2ea..ddf9d84 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
@@ -21,11 +21,13 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
 import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 
 public class SharedRefDatabaseWrapper implements GlobalRefDatabase {
+  private static final GlobalRefDatabase NOOP_REFDB = new NoopSharedRefDatabase();
 
   @Inject(optional = true)
   private DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem;
@@ -95,6 +97,6 @@
   }
 
   private GlobalRefDatabase sharedRefDb() {
-    return sharedRefDbDynamicItem.get();
+    return Optional.ofNullable(sharedRefDbDynamicItem).map(di -> di.get()).orElse(NOOP_REFDB);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandler.java
index 77c19c1..2990264 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandler.java
@@ -20,6 +20,7 @@
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
 import java.util.concurrent.Executor;
 
@@ -45,7 +46,7 @@
     }
   }
 
-  class CacheEvictionTask implements Runnable {
+  class CacheEvictionTask extends ForwarderTask {
     CacheEvictionEvent cacheEvictionEvent;
 
     CacheEvictionTask(CacheEvictionEvent cacheEvictionEvent) {
@@ -54,7 +55,7 @@
 
     @Override
     public void run() {
-      forwarders.forEach(f -> f.evict(cacheEvictionEvent));
+      forwarders.forEach(f -> f.evict(this, cacheEvictionEvent));
     }
 
     @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java
index acf8df0..b8521a3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/CachePatternMatcher.java
@@ -26,8 +26,7 @@
 @Singleton
 class CachePatternMatcher {
   private static final List<String> DEFAULT_PATTERNS =
-      ImmutableList.of(
-          "accounts", "^groups.*", "ldap_groups", "ldap_usernames", "projects", "sshkeys");
+      ImmutableList.of("^groups.*", "ldap_groups", "ldap_usernames", "projects", "sshkeys");
 
   private final Pattern pattern;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
index 5723fbe..2e2f9de 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandler.java
@@ -20,7 +20,9 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
 import java.util.concurrent.Executor;
@@ -30,12 +32,16 @@
 
   private final DynamicSet<ProjectListUpdateForwarder> forwarders;
   private final Executor executor;
+  private final ProjectsFilter projectsFilter;
 
   @Inject
   public ProjectListUpdateHandler(
-      DynamicSet<ProjectListUpdateForwarder> forwarders, @CacheExecutor Executor executor) {
+      DynamicSet<ProjectListUpdateForwarder> forwarders,
+      @CacheExecutor Executor executor,
+      ProjectsFilter filter) {
     this.forwarders = forwarders;
     this.executor = executor;
+    this.projectsFilter = filter;
   }
 
   @Override
@@ -51,13 +57,13 @@
   }
 
   private void process(ProjectEvent event, boolean delete) {
-    if (!Context.isForwardedEvent()) {
+    if (!Context.isForwardedEvent() && projectsFilter.matches(event.getProjectName())) {
       executor.execute(
           new ProjectListUpdateTask(new ProjectListUpdateEvent(event.getProjectName(), delete)));
     }
   }
 
-  class ProjectListUpdateTask implements Runnable {
+  class ProjectListUpdateTask extends ForwarderTask {
     private final ProjectListUpdateEvent projectListUpdateEvent;
 
     ProjectListUpdateTask(ProjectListUpdateEvent projectListUpdateEvent) {
@@ -66,7 +72,7 @@
 
     @Override
     public void run() {
-      forwarders.forEach(f -> f.updateProjectList(projectListUpdateEvent));
+      forwarders.forEach(f -> f.updateProjectList(this, projectListUpdateEvent));
     }
 
     @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
index aafd59f..a1b7a53 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventHandler.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
 import java.util.concurrent.Executor;
@@ -26,17 +27,24 @@
 class EventHandler implements EventListener {
   private final Executor executor;
   private final DynamicSet<StreamEventForwarder> forwarders;
+  private final ProjectsFilter projectsFilter;
 
   @Inject
-  EventHandler(DynamicSet<StreamEventForwarder> forwarders, @EventExecutor Executor executor) {
+  EventHandler(
+      DynamicSet<StreamEventForwarder> forwarders,
+      @EventExecutor Executor executor,
+      ProjectsFilter projectsFilter) {
     this.forwarders = forwarders;
     this.executor = executor;
+    this.projectsFilter = projectsFilter;
   }
 
   @Override
   public void onEvent(Event event) {
     if (!Context.isForwardedEvent() && event instanceof ProjectEvent) {
-      executor.execute(new EventTask(event));
+      if (projectsFilter.matches(event)) {
+        executor.execute(new EventTask(event));
+      }
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheEvictionForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheEvictionForwarder.java
index 68921e6..2a18759 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheEvictionForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/CacheEvictionForwarder.java
@@ -20,8 +20,9 @@
   /**
    * Forward a cache eviction event to the other master.
    *
+   * @param task that triggered the forwarding of the cache event.
    * @param cacheEvictionEvent the details of the cache eviction event.
    * @return true if successful, otherwise false.
    */
-  boolean evict(CacheEvictionEvent cacheEvictionEvent);
+  boolean evict(ForwarderTask task, CacheEvictionEvent cacheEvictionEvent);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwarderTask.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwarderTask.java
new file mode 100644
index 0000000..329b5cb
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwarderTask.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 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.multisite.forwarder;
+
+public abstract class ForwarderTask implements Runnable {
+  private final Thread callerThread = Thread.currentThread();
+
+  public Thread getCallerThread() {
+    return callerThread;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/IndexEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/IndexEventForwarder.java
index 5c1f444..73cd9e8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/IndexEventForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/IndexEventForwarder.java
@@ -21,16 +21,18 @@
   /**
    * Publish an indexing event to the broker using interactive topic.
    *
+   * @param task that triggered the forwarding of the index event.
    * @param event the details of the index event.
    * @return true if successful, otherwise false.
    */
-  boolean index(IndexEvent event);
+  boolean index(ForwarderTask task, IndexEvent event);
 
   /**
    * Publish an indexing event to the broker using batch topic.
    *
+   * @param task that triggered the forwarding of the index event.
    * @param event the details of the index event.
    * @return true if successful, otherwise false.
    */
-  boolean batchIndex(IndexEvent event);
+  boolean batchIndex(ForwarderTask task, IndexEvent event);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ProjectListUpdateForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ProjectListUpdateForwarder.java
index e40ff3f..b370b26 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ProjectListUpdateForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ProjectListUpdateForwarder.java
@@ -21,8 +21,9 @@
   /**
    * Forward an update the project list cache event to the other master.
    *
+   * @param task that triggered the forwarding of the project list event.
    * @param projectListUpdateEvent the content of project list update event
    * @return true if successful, otherwise false.
    */
-  boolean updateProjectList(ProjectListUpdateEvent projectListUpdateEvent);
+  boolean updateProjectList(ForwarderTask task, ProjectListUpdateEvent projectListUpdateEvent);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
index b32e5ae..1e2a48f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
@@ -19,22 +19,21 @@
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 
 @Singleton
-public class BrokerCacheEvictionForwarder implements CacheEvictionForwarder {
-  private final BrokerApiWrapper broker;
-  private final Configuration cfg;
+public class BrokerCacheEvictionForwarder extends BrokerForwarder
+    implements CacheEvictionForwarder {
 
   @Inject
   BrokerCacheEvictionForwarder(BrokerApiWrapper broker, Configuration cfg) {
-    this.broker = broker;
-    this.cfg = cfg;
+    super(broker, cfg);
   }
 
   @Override
-  public boolean evict(CacheEvictionEvent event) {
-    return broker.send(EventTopic.CACHE_TOPIC.topic(cfg), event);
+  public boolean evict(ForwarderTask task, CacheEvictionEvent event) {
+    return send(task, EventTopic.CACHE_TOPIC, event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarder.java
new file mode 100644
index 0000000..da19fed
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarder.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 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.multisite.forwarder.broker;
+
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.MultiSiteEvent;
+
+public abstract class BrokerForwarder {
+  private static final CharSequence HIGH_AVAILABILITY_PLUGIN = "/plugins/high-availability/";
+  private static final CharSequence HIGH_AVAILABILITY_FORWARDER = "Forwarded-Index-Event";
+
+  private final BrokerApiWrapper broker;
+  private final Configuration cfg;
+
+  protected BrokerForwarder(BrokerApiWrapper broker, Configuration cfg) {
+    this.broker = broker;
+    this.cfg = cfg;
+  }
+
+  protected boolean currentThreadBelongsToHighAvailabilityPlugin(ForwarderTask task) {
+    String currentThreadName = task.getCallerThread().getName();
+
+    return currentThreadName.contains(HIGH_AVAILABILITY_PLUGIN)
+        || currentThreadName.contains(HIGH_AVAILABILITY_FORWARDER);
+  }
+
+  protected boolean send(ForwarderTask task, EventTopic eventTopic, MultiSiteEvent event) {
+    // Events generated by the high-availability plugin should be
+    // discarded. Sending them around would cause infinite loops.
+    if (currentThreadBelongsToHighAvailabilityPlugin(task)) {
+      return true;
+    }
+
+    return broker.send(eventTopic.topic(cfg), event);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
index a86c62e..8321b94 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
@@ -17,27 +17,25 @@
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.IndexEvent;
 
-public class BrokerIndexEventForwarder implements IndexEventForwarder {
-  private final BrokerApiWrapper broker;
-  private final Configuration cfg;
+public class BrokerIndexEventForwarder extends BrokerForwarder implements IndexEventForwarder {
 
   @Inject
   BrokerIndexEventForwarder(BrokerApiWrapper broker, Configuration cfg) {
-    this.broker = broker;
-    this.cfg = cfg;
+    super(broker, cfg);
   }
 
   @Override
-  public boolean index(IndexEvent event) {
-    return broker.send(EventTopic.INDEX_TOPIC.topic(cfg), event);
+  public boolean index(ForwarderTask task, IndexEvent event) {
+    return send(task, EventTopic.INDEX_TOPIC, event);
   }
 
   @Override
-  public boolean batchIndex(IndexEvent event) {
-    return broker.send(EventTopic.BATCH_INDEX_TOPIC.topic(cfg), event);
+  public boolean batchIndex(ForwarderTask task, IndexEvent event) {
+    return send(task, EventTopic.BATCH_INDEX_TOPIC, event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
index 34e0300..dc8139d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
@@ -20,22 +20,21 @@
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
 
 @Singleton
-public class BrokerProjectListUpdateForwarder implements ProjectListUpdateForwarder {
-  private final BrokerApiWrapper broker;
-  private final Configuration cfg;
+public class BrokerProjectListUpdateForwarder extends BrokerForwarder
+    implements ProjectListUpdateForwarder {
 
   @Inject
   BrokerProjectListUpdateForwarder(BrokerApiWrapper broker, Configuration cfg) {
-    this.broker = broker;
-    this.cfg = cfg;
+    super(broker, cfg);
   }
 
   @Override
-  public boolean updateProjectList(ProjectListUpdateEvent event) {
-    return broker.send(PROJECT_LIST_TOPIC.topic(cfg), event);
+  public boolean updateProjectList(ForwarderTask task, ProjectListUpdateEvent event) {
+    return send(task, PROJECT_LIST_TOPIC, event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
index 6ba18ed..02972b0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
@@ -79,7 +79,7 @@
   @Override
   public Optional<ChangeNotes> getChangeNotes() {
     try (ManualRequestContext ctx = oneOffReqCtx.open()) {
-      this.changeNotes = Optional.ofNullable(changeFinder.findOne(changeId));
+      this.changeNotes = changeFinder.findOne(changeId);
       return changeNotes;
     }
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
index 649074b..5effd0e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -21,7 +21,9 @@
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.AccountIndexEvent;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
@@ -44,6 +46,7 @@
   private final DynamicSet<IndexEventForwarder> forwarders;
   private final Set<IndexTask> queuedTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
   private final ChangeCheckerImpl.Factory changeChecker;
+  private final ProjectsFilter projectsFilter;
   private final GroupChecker groupChecker;
 
   @Inject
@@ -51,10 +54,12 @@
       @IndexExecutor Executor executor,
       DynamicSet<IndexEventForwarder> forwarders,
       ChangeCheckerImpl.Factory changeChecker,
+      ProjectsFilter projectsFilter,
       GroupChecker groupChecker) {
     this.forwarders = forwarders;
     this.executor = executor;
     this.changeChecker = changeChecker;
+    this.projectsFilter = projectsFilter;
     this.groupChecker = groupChecker;
   }
 
@@ -91,7 +96,7 @@
 
   @Override
   public void onProjectIndexed(String projectName) {
-    if (!Context.isForwardedEvent()) {
+    if (!Context.isForwardedEvent() && projectsFilter.matches(projectName)) {
       IndexProjectTask task = new IndexProjectTask(new ProjectIndexEvent(projectName));
       if (queuedTasks.add(task)) {
         executor.execute(task);
@@ -100,7 +105,7 @@
   }
 
   private void executeIndexChangeTask(String projectName, int id) {
-    if (!Context.isForwardedEvent()) {
+    if (!Context.isForwardedEvent() && projectsFilter.matches(projectName)) {
       ChangeChecker checker = changeChecker.create(projectName + "~" + id);
 
       try {
@@ -134,7 +139,8 @@
     }
   }
 
-  abstract class IndexTask implements Runnable {
+  abstract class IndexTask extends ForwarderTask {
+
     @Override
     public void run() {
       queuedTasks.remove(this);
@@ -153,7 +159,7 @@
 
     @Override
     public void execute() {
-      forwarders.forEach(f -> f.index(changeIndexEvent));
+      forwarders.forEach(f -> f.index(this, changeIndexEvent));
     }
 
     @Override
@@ -184,7 +190,7 @@
 
     @Override
     public void execute() {
-      forwarders.forEach(f -> f.batchIndex(changeIndexEvent));
+      forwarders.forEach(f -> f.batchIndex(this, changeIndexEvent));
     }
 
     @Override
@@ -215,7 +221,7 @@
 
     @Override
     public void execute() {
-      forwarders.forEach(f -> f.index(accountIndexEvent));
+      forwarders.forEach(f -> f.index(this, accountIndexEvent));
     }
 
     @Override
@@ -246,7 +252,7 @@
 
     @Override
     public void execute() {
-      forwarders.forEach(f -> f.index(groupIndexEvent));
+      forwarders.forEach(f -> f.index(this, groupIndexEvent));
     }
 
     @Override
@@ -277,7 +283,7 @@
 
     @Override
     public void execute() {
-      forwarders.forEach(f -> f.index(projectIndexEvent));
+      forwarders.forEach(f -> f.index(this, projectIndexEvent));
     }
 
     @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java
index ba2a7d6..8655824 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ProjectCheckerImpl.java
@@ -30,6 +30,8 @@
 
   @Override
   public boolean isUpToDate(Optional<ProjectIndexEvent> indexEvent) {
-    return indexEvent.map(e -> Project.nameKey(e.projectName)).map(projectCache::get).isPresent();
+    return indexEvent
+        .flatMap(event -> projectCache.get(Project.nameKey(event.projectName)))
+        .isPresent();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
index 7aa3328..0afe308 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
@@ -18,6 +18,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
@@ -51,9 +52,17 @@
       ValidationMetrics validationMetrics,
       SharedRefEnforcement refEnforcement,
       LockWrapper.Factory lockWrapperFactory,
+      ProjectsFilter projectsFilter,
       @Assisted String projectName,
       @Assisted RefDatabase refDb) {
-    super(sharedRefDb, validationMetrics, refEnforcement, lockWrapperFactory, projectName, refDb);
+    super(
+        sharedRefDb,
+        validationMetrics,
+        refEnforcement,
+        lockWrapperFactory,
+        projectsFilter,
+        projectName,
+        refDb);
   }
 
   public void executeBatchUpdateWithValidation(
@@ -61,7 +70,8 @@
       NoParameterVoidFunction batchRefUpdateFunction,
       OneParameterVoidFunction<List<ReceiveCommand>> batchRefUpdateRollbackFunction)
       throws IOException {
-    if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
+    if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED
+        || !isGlobalProject(projectName)) {
       batchRefUpdateFunction.invoke();
       return;
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
index 73bab49..c8ddb43 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -31,6 +31,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import java.io.IOException;
@@ -58,6 +59,7 @@
   private final GitRepositoryManager gitRepositoryManager;
   private final GitReferenceUpdated gitReferenceUpdated;
   private final ProjectVersionLogger verLogger;
+  private final ProjectsFilter projectsFilter;
 
   protected final SharedRefDatabaseWrapper sharedRefDb;
 
@@ -66,11 +68,13 @@
       GitRepositoryManager gitRepositoryManager,
       SharedRefDatabaseWrapper sharedRefDb,
       GitReferenceUpdated gitReferenceUpdated,
-      ProjectVersionLogger verLogger) {
+      ProjectVersionLogger verLogger,
+      ProjectsFilter projectsFilter) {
     this.gitRepositoryManager = gitRepositoryManager;
     this.sharedRefDb = sharedRefDb;
     this.gitReferenceUpdated = gitReferenceUpdated;
     this.verLogger = verLogger;
+    this.projectsFilter = projectsFilter;
   }
 
   @Override
@@ -78,7 +82,9 @@
     logger.atFine().log("Processing event type: " + event.type);
     // Producer of the Event use RefUpdatedEvent to trigger the version update
     if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
-      updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
+      if (projectsFilter.matches(event)) {
+        updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
+      }
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
index d7fafe0..c110f0f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
@@ -21,6 +21,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
@@ -46,6 +47,7 @@
   private final LockWrapper.Factory lockWrapperFactory;
   protected final RefDatabase refDb;
   protected final SharedRefEnforcement refEnforcement;
+  protected final ProjectsFilter projectsFilter;
 
   public static interface Factory {
     RefUpdateValidator create(String projectName, RefDatabase refDb);
@@ -82,6 +84,7 @@
       ValidationMetrics validationMetrics,
       SharedRefEnforcement refEnforcement,
       LockWrapper.Factory lockWrapperFactory,
+      ProjectsFilter projectsFilter,
       @Assisted String projectName,
       @Assisted RefDatabase refDb) {
     this.sharedRefDb = sharedRefDb;
@@ -90,6 +93,7 @@
     this.refDb = refDb;
     this.projectName = projectName;
     this.refEnforcement = refEnforcement;
+    this.projectsFilter = projectsFilter;
   }
 
   public RefUpdate.Result executeRefUpdate(
@@ -98,6 +102,7 @@
       OneParameterFunction<ObjectId, Result> rollbackFunction)
       throws IOException {
     if (isProjectVersionUpdate(refUpdate.getName())
+        || !isGlobalProject(projectName)
         || refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
       return refUpdateFunction.invoke();
     }
@@ -123,6 +128,12 @@
     }
   }
 
+  protected Boolean isGlobalProject(String projectName) {
+    Boolean isGlobalProject = projectsFilter.matches(projectName);
+    logger.atFine().log("Is global project? " + isGlobalProject);
+    return isGlobalProject;
+  }
+
   protected RefUpdate.Result doExecuteRefUpdate(
       RefUpdate refUpdate,
       NoParameterFunction<Result> refUpdateFunction,
@@ -256,7 +267,7 @@
   }
 
   protected Ref getCurrentRef(String refName) throws IOException {
-    return MoreObjects.firstNonNull(refDb.getRef(refName), nullRef(refName));
+    return MoreObjects.firstNonNull(refDb.findRef(refName), nullRef(refName));
   }
 
   public static class CloseableSet<T extends AutoCloseable> implements AutoCloseable {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index 481d288..4a34b7a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -14,7 +14,6 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -33,8 +32,6 @@
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 
 public class ValidationModule extends FactoryModule {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final Configuration cfg;
 
   public ValidationModule(Configuration cfg) {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 67e942f..a76545f 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -96,4 +96,28 @@
 
     Ignore the alignment with the shared ref-database for AProject on refs/heads/feature.
 
-    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
\ No newline at end of file
+    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
+
+```projects.pattern```
+:   Specifies which projects events should be send via broker. It can be provided more
+    than once, and supports three formats: regular expressions, wildcard matching, and single
+    project matching. All three formats match case-sensitive.
+
+    Values starting with a caret `^` are treated as regular
+    expressions. For the regular expressions details please follow
+    official [java documentation](https://docs.oracle.com/javase/tutorial/essential/regex/).
+
+    Please note that regular expressions could also be used
+    with inverse match.
+
+    Values that are not regular expressions and end in `*` are
+    treated as wildcard matches. Wildcards match projects whose
+    name agrees from the beginning until the trailing `*`. So
+    `foo/b*` would match the projects `foo/b`, `foo/bar`, and
+    `foo/baz`, but neither `foobar`, nor `bar/foo/baz`.
+
+    Values that are neither regular expressions nor wildcards are
+    treated as single project matches. So `foo/bar` matches only
+    the project `foo/bar`, but no other project.
+
+    By default, all projects are matched.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java
new file mode 100644
index 0000000..e81c906
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/ProjectsFilterTest.java
@@ -0,0 +1,180 @@
+// Copyright (C) 2020 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.multisite;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.ProjectEvent;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.testing.GerritJUnit;
+import com.googlesource.gerrit.plugins.multisite.Configuration.Projects;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectsFilterTest {
+
+  @Mock private Configuration configuration;
+  @Mock private Projects projects;
+
+  private ProjectsFilter objectUnderTest;
+
+  @Test
+  public void shouldMatchByExactProjectName() {
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isFalse();
+  }
+
+  @Test
+  public void shouldMatchByWildcard() {
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project*"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("2test_project"))).isFalse();
+  }
+
+  @Test
+  public void shouldMatchByRegex() {
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("^test_(project|project2)"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
+  }
+
+  @Test
+  public void shouldExcludeByRegex() {
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("^(?:(?!test_project3).)*$"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
+  }
+
+  @Test
+  public void shouldExcludeByMultipleProjectsRegexPattern() {
+    when(projects.getPatterns())
+        .thenReturn(Lists.newArrayList("^(?:(?!(test_project3|test_project4)).)*$"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project2"))).isTrue();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project3"))).isFalse();
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project4"))).isFalse();
+  }
+
+  @Test
+  public void shouldMatchWhenNoPatternProvided() {
+    when(projects.getPatterns()).thenReturn(Collections.emptyList());
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(NameKey.parse("test_project"))).isTrue();
+  }
+
+  @Test
+  public void shouldMatchProjectEvent() {
+    ProjectEvent event = mock(ProjectEvent.class);
+    when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project"));
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(event)).isTrue();
+  }
+
+  @Test
+  public void shouldMatchRefUpdatedEvent() {
+    RefUpdatedEvent event = mock(RefUpdatedEvent.class);
+    when(event.getProjectNameKey()).thenReturn(NameKey.parse("test_project"));
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(event)).isTrue();
+  }
+
+  @Test
+  public void shouldExcludedNonProjectEvents() {
+    Event event = mock(Event.class);
+    when(projects.getPatterns()).thenReturn(Lists.newArrayList("test_project)"));
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    assertThat(objectUnderTest.matches(event)).isFalse();
+  }
+
+  @Test
+  public void shouldThrowExceptionWhenProjecNameIsNull() {
+    when(projects.getPatterns()).thenReturn(Collections.emptyList());
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    GerritJUnit.assertThrows(
+        IllegalArgumentException.class, () -> objectUnderTest.matches((NameKey) null));
+  }
+
+  @Test
+  public void shouldThrowExceptionWhenProjecNameIsEmpty() {
+    when(projects.getPatterns()).thenReturn(Collections.emptyList());
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    GerritJUnit.assertThrows(
+        IllegalArgumentException.class, () -> objectUnderTest.matches(NameKey.parse("")));
+  }
+
+  @Test
+  public void shouldThrowExceptionWhenEventIsNull() {
+    when(projects.getPatterns()).thenReturn(Collections.emptyList());
+    when(configuration.projects()).thenReturn(projects);
+
+    objectUnderTest = new ProjectsFilter(configuration);
+
+    GerritJUnit.assertThrows(
+        IllegalArgumentException.class, () -> objectUnderTest.matches((Event) null));
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java
new file mode 100644
index 0000000..67be583
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CacheEvictionHandlerTest.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2020 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.multisite.cache;
+
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+import java.util.concurrent.Executor;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CacheEvictionHandlerTest {
+
+  @Mock private Executor executorMock;
+  private CachePatternMatcher defaultCacheMatcher =
+      new CachePatternMatcher(new Configuration(new Config(), new Config()));
+
+  @Test
+  public void shouldNotPublishAccountsCacheEvictions() {
+
+    final CacheEvictionHandler<String, String> handler =
+        new CacheEvictionHandler<>(DynamicSet.emptySet(), executorMock, defaultCacheMatcher);
+
+    handler.onRemoval(
+        "test", "accounts", RemovalNotification.create("test", "accounts", RemovalCause.EXPLICIT));
+
+    verifyZeroInteractions(executorMock);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java
index 052a9ae..b35e862 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/CachePattenMatcherTest.java
@@ -37,7 +37,6 @@
     CachePatternMatcher matcher = new CachePatternMatcher(configurationMock);
     for (String cache :
         ImmutableList.of(
-            "accounts",
             "groups",
             "groups_byinclude",
             "groups_byname",
@@ -55,6 +54,7 @@
     }
     for (String cache :
         ImmutableList.of(
+            "accounts",
             "adv_bases",
             "change_kind",
             "change_notes",
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
index bf4b4cf..2a25c37 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/cache/ProjectListUpdateHandlerTest.java
@@ -15,7 +15,10 @@
 package com.googlesource.gerrit.plugins.multisite.cache;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -24,6 +27,7 @@
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.cache.ProjectListUpdateHandler.ProjectListUpdateTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
@@ -40,10 +44,14 @@
   private ProjectListUpdateHandler handler;
 
   @Mock private ProjectListUpdateForwarder forwarder;
+  @Mock private ProjectsFilter projectsFilter;
 
   @Before
   public void setUp() {
-    handler = new ProjectListUpdateHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor());
+    when(projectsFilter.matches(any(String.class))).thenReturn(true);
+    handler =
+        new ProjectListUpdateHandler(
+            asDynamicSet(forwarder), MoreExecutors.directExecutor(), projectsFilter);
   }
 
   private DynamicSet<ProjectListUpdateForwarder> asDynamicSet(
@@ -59,7 +67,9 @@
     NewProjectCreatedListener.Event event = mock(NewProjectCreatedListener.Event.class);
     when(event.getProjectName()).thenReturn(projectName);
     handler.onNewProjectCreated(event);
-    verify(forwarder).updateProjectList(new ProjectListUpdateEvent(projectName, false));
+    verify(forwarder)
+        .updateProjectList(
+            any(ProjectListUpdateTask.class), eq(new ProjectListUpdateEvent(projectName, false)));
   }
 
   @Test
@@ -68,7 +78,9 @@
     ProjectDeletedListener.Event event = mock(ProjectDeletedListener.Event.class);
     when(event.getProjectName()).thenReturn(projectName);
     handler.onProjectDeleted(event);
-    verify(forwarder).updateProjectList(new ProjectListUpdateEvent(projectName, true));
+    verify(forwarder)
+        .updateProjectList(
+            any(ProjectListUpdateTask.class), eq(new ProjectListUpdateEvent(projectName, true)));
   }
 
   @Test
@@ -81,6 +93,18 @@
   }
 
   @Test
+  public void shouldNotForwardIfFilteredOutByProjectName() throws Exception {
+    when(projectsFilter.matches(any(String.class))).thenReturn(false);
+    String projectName = "projectToAdd";
+    NewProjectCreatedListener.Event event = mock(NewProjectCreatedListener.Event.class);
+    when(event.getProjectName()).thenReturn(projectName);
+    handler.onNewProjectCreated(event);
+    verify(forwarder, never())
+        .updateProjectList(
+            any(ProjectListUpdateTask.class), eq(new ProjectListUpdateEvent(projectName, true)));
+  }
+
+  @Test
   public void testProjectUpdateTaskToString() throws Exception {
     String projectName = "someProjectName";
     ProjectListUpdateTask task =
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
index 990a03f..bac0851 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/EventHandlerTest.java
@@ -15,15 +15,19 @@
 package com.googlesource.gerrit.plugins.multisite.event;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.event.EventHandler.EventTask;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
@@ -39,10 +43,13 @@
   private EventHandler eventHandler;
 
   @Mock private StreamEventForwarder forwarder;
+  @Mock private ProjectsFilter projectsFilter;
 
   @Before
   public void setUp() {
-    eventHandler = new EventHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor());
+    when(projectsFilter.matches(any(ProjectEvent.class))).thenReturn(true);
+    eventHandler =
+        new EventHandler(asDynamicSet(forwarder), MoreExecutors.directExecutor(), projectsFilter);
   }
 
   private DynamicSet<StreamEventForwarder> asDynamicSet(StreamEventForwarder forwarder) {
@@ -53,7 +60,7 @@
 
   @Test
   public void shouldForwardAnyProjectEvent() throws Exception {
-    Event event = mock(ProjectEvent.class);
+    ProjectEvent event = mock(ProjectEvent.class);
     eventHandler.onEvent(event);
     verify(forwarder).send(event);
   }
@@ -73,6 +80,16 @@
   }
 
   @Test
+  public void shouldNotForwardIfFilteredOutByProjectName() throws Exception {
+    when(projectsFilter.matches(any(ProjectEvent.class))).thenReturn(false);
+
+    ProjectEvent event = mock(ProjectEvent.class);
+
+    eventHandler.onEvent(event);
+    verify(forwarder, never()).send(event);
+  }
+
+  @Test
   public void tesEventTaskToString() throws Exception {
     Event event = new RefUpdatedEvent();
     EventTask task = eventHandler.new EventTask(event);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/BrokerForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/BrokerForwarderTest.java
new file mode 100644
index 0000000..5e2fc05
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/BrokerForwarderTest.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2020 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.multisite.forwarder;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.MultiSiteEvent;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BrokerForwarderTest {
+  private static final String HIGH_AVAILABILITY_PLUGIN = "/plugins/high-availability/";
+  private static final String HIGH_AVAILABILITY_FORWARDED = "Forwarded-Index-Event";
+  private static final long TEST_TIMEOUT_SEC = 5L;
+
+  @Mock private BrokerApiWrapper brokerMock;
+
+  private TestBrokerForwarder brokerForwarder;
+
+  private Configuration cfg;
+
+  private EventTopic testTopic;
+
+  private String testTopicName;
+
+  private TestEvent testEvent;
+
+  private ExecutorService executor;
+
+  public class TestBrokerForwarder extends BrokerForwarder {
+
+    TestBrokerForwarder() {
+      super(brokerMock, cfg);
+    }
+
+    public void send(ForwarderTask task, EventTopic eventTopic, TestEvent testEvent) {
+      super.send(task, eventTopic, testEvent);
+    }
+  }
+
+  public class TestEvent extends MultiSiteEvent {
+
+    protected TestEvent() {
+      super("test");
+    }
+  }
+
+  @Before
+  public void setup() {
+    cfg = new Configuration(new Config(), new Config());
+    testTopic = EventTopic.INDEX_TOPIC;
+    testTopicName = testTopic.topic(cfg);
+    testEvent = new TestEvent();
+    brokerForwarder = new TestBrokerForwarder();
+    executor = Executors.newSingleThreadExecutor();
+  }
+
+  @After
+  public void teardown() {
+    executor.shutdown();
+  }
+
+  @Test
+  public void shouldSendEventToBrokerFromGenericSourceThread() {
+    brokerForwarder.send(newForwarderTask(), testTopic, testEvent);
+    verify(brokerMock).send(eq(testTopicName), eq(testEvent));
+  }
+
+  @Test
+  public void shouldSkipEventFromHighAvailabilityPluginThread() {
+    brokerForwarder.send(newForwarderTask(HIGH_AVAILABILITY_PLUGIN), testTopic, testEvent);
+    verifyZeroInteractions(brokerMock);
+  }
+
+  @Test
+  public void shouldSkipEventFromHighAvailabilityPluginForwardedThread() {
+    brokerForwarder.send(newForwarderTask(HIGH_AVAILABILITY_FORWARDED), testTopic, testEvent);
+
+    verifyZeroInteractions(brokerMock);
+  }
+
+  private ForwarderTask newForwarderTask(String threadName) {
+    try {
+      return executor
+          .submit(
+              () -> {
+                Thread.currentThread().setName(threadName);
+                return newForwarderTask();
+              })
+          .get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
+    } catch (InterruptedException | ExecutionException | TimeoutException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private ForwarderTask newForwarderTask() {
+    return new ForwarderTask() {
+
+      @Override
+      public void run() {}
+    };
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java
index 747da79..90ae8da 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedCacheEvictionHandlerTest.java
@@ -15,11 +15,10 @@
 package com.googlesource.gerrit.plugins.multisite.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
 
 import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.googlesource.gerrit.plugins.multisite.cache.Constants;
@@ -30,19 +29,19 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ForwardedCacheEvictionHandlerTest {
 
   @Rule public ExpectedException exception = ExpectedException.none();
   @Mock private DynamicMap<Cache<?, ?>> cacheMapMock;
-  @Mock private Cache<?, ?> cacheMock;
+  private Cache<Object, Object> cacheUnderTest;
   private ForwardedCacheEvictionHandler handler;
 
   @Before
   public void setUp() throws Exception {
     handler = new ForwardedCacheEvictionHandler(cacheMapMock);
+    cacheUnderTest = CacheBuilder.newBuilder().build();
   }
 
   @Test
@@ -58,65 +57,22 @@
   @Test
   public void testSuccessfulCacheEviction() throws Exception {
     CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(123));
-    doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
+    cacheUnderTest.put(entry.getKey(), new Object());
+    doReturn(cacheUnderTest).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
 
     handler.evict(entry);
-    verify(cacheMock).invalidate(entry.getKey());
+    assertThat(cacheUnderTest.getIfPresent(entry.getKey())).isNull();
   }
 
   @Test
   public void testSuccessfulProjectListCacheEviction() throws Exception {
     CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.PROJECT_LIST, null);
-    doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
+    cacheUnderTest.put("foo", new Object());
+    cacheUnderTest.put("bar", new Object());
+    doReturn(cacheUnderTest).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
 
     handler.evict(entry);
-    verify(cacheMock).invalidateAll();
-  }
-
-  @Test
-  public void shouldSetAndUnsetForwardedContext() throws Exception {
-    CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(456));
-    doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
-
-    // this doAnswer is to allow to assert that context is set to forwarded
-    // while cache eviction is called.
-    doAnswer(
-            (Answer<Void>)
-                invocation -> {
-                  assertThat(Context.isForwardedEvent()).isTrue();
-                  return null;
-                })
-        .when(cacheMock)
-        .invalidate(entry.getKey());
-
-    assertThat(Context.isForwardedEvent()).isFalse();
-    handler.evict(entry);
-    assertThat(Context.isForwardedEvent()).isFalse();
-
-    verify(cacheMock).invalidate(entry.getKey());
-  }
-
-  @Test
-  public void shouldSetAndUnsetForwardedContextEvenIfExceptionIsThrown() throws Exception {
-    CacheEntry entry = new CacheEntry(Constants.GERRIT, Constants.ACCOUNTS, Account.id(789));
-    doReturn(cacheMock).when(cacheMapMock).get(entry.getPluginName(), entry.getCacheName());
-
-    doAnswer(
-            (Answer<Void>)
-                invocation -> {
-                  assertThat(Context.isForwardedEvent()).isTrue();
-                  throw new RuntimeException();
-                })
-        .when(cacheMock)
-        .invalidate(entry.getKey());
-
-    assertThat(Context.isForwardedEvent()).isFalse();
-    try {
-      handler.evict(entry);
-    } catch (RuntimeException e) {
-    }
-    assertThat(Context.isForwardedEvent()).isFalse();
-
-    verify(cacheMock).invalidate(entry.getKey());
+    assertThat(cacheUnderTest.getIfPresent("foo")).isNull();
+    assertThat(cacheUnderTest.getIfPresent("bar")).isNull();
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
new file mode 100644
index 0000000..022dec5
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandlerTest.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2020 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.multisite.index;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
+import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent;
+import com.googlesource.gerrit.plugins.multisite.index.IndexEventHandler.IndexProjectTask;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IndexEventHandlerTest {
+
+  private IndexEventHandler eventHandler;
+
+  @Mock private ProjectsFilter projectsFilter;
+  @Mock private IndexEventForwarder forwarder;
+  @Mock private ChangeCheckerImpl.Factory changeChecker;
+
+  @Before
+  public void setUp() {
+    eventHandler =
+        new IndexEventHandler(
+            MoreExecutors.directExecutor(),
+            asDynamicSet(forwarder),
+            changeChecker,
+            projectsFilter,
+            new TestGroupChecker(true));
+  }
+
+  private DynamicSet<IndexEventForwarder> asDynamicSet(IndexEventForwarder forwarder) {
+    DynamicSet<IndexEventForwarder> result = new DynamicSet<>();
+    result.add("multi-site", forwarder);
+    return result;
+  }
+
+  @Test
+  public void shouldNotForwardProjectIndexedIfFilteredOutByProjectName() throws Exception {
+    when(projectsFilter.matches(any(String.class))).thenReturn(false);
+
+    eventHandler.onProjectIndexed("test_project");
+    verify(forwarder, never())
+        .index(any(IndexProjectTask.class), eq(new ProjectIndexEvent("test_project")));
+  }
+
+  @Test
+  public void shouldNotForwardIndexChangeIfFilteredOutByProjectName() throws Exception {
+    int changeId = 1;
+    when(projectsFilter.matches(any(String.class))).thenReturn(false);
+
+    eventHandler.onChangeIndexed("test_project", changeId);
+    verifyZeroInteractions(changeChecker);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
index b79740d..41bebc2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
@@ -16,14 +16,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
-import static org.mockito.Mockito.any;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
@@ -66,12 +69,15 @@
   @Mock SharedRefDatabaseWrapper sharedRefDatabase;
 
   @Mock SharedRefEnforcement tmpRefEnforcement;
+
+  @Mock ProjectsFilter projectsFilter;
+
   @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction;
 
   @Before
   public void setup() throws Exception {
     super.setUp();
-
+    when(projectsFilter.matches(anyString())).thenReturn(true);
     gitRepoSetup();
   }
 
@@ -142,6 +148,24 @@
         (command) -> assertThat(command.getResult()).isEqualTo(ReceiveCommand.Result.LOCK_FAILURE));
   }
 
+  @Test
+  public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception {
+    when(projectsFilter.matches(anyString())).thenReturn(false);
+
+    String AN_OUT_OF_SYNC_REF = "refs/changes/01/1/1";
+    BatchRefUpdate batchRefUpdate =
+        newBatchUpdate(
+            Collections.singletonList(new ReceiveCommand(A, B, AN_OUT_OF_SYNC_REF, UPDATE)));
+    BatchRefUpdateValidator batchRefUpdateValidator =
+        getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement);
+
+    batchRefUpdateValidator.executeBatchUpdateWithValidation(
+        batchRefUpdate, () -> execute(batchRefUpdate), this::defaultRollback);
+
+    verify(sharedRefDatabase, never())
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
+  }
+
   private BatchRefUpdateValidator newDefaultValidator(String projectName) {
     return getRefValidatorForEnforcement(projectName, new DefaultSharedRefEnforcement());
   }
@@ -153,6 +177,7 @@
         new ValidationMetrics(new DisabledMetricMaker()),
         sharedRefEnforcement,
         new DummyLockWrapper(),
+        projectsFilter,
         projectName,
         diskRepo.getRefDatabase());
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
index e9651fb..4aa4ba1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.multisite.validation;
 
 import static java.util.Arrays.asList;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -24,6 +25,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterVoidFunction;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
@@ -40,6 +42,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -58,6 +61,7 @@
   @Mock RevWalk revWalk;
   @Mock ProgressMonitor progressMonitor;
   @Mock ValidationMetrics validationMetrics;
+  @Mock ProjectsFilter projectsFilter;
   @Mock OneParameterVoidFunction<List<ReceiveCommand>> rollbackFunction;
 
   private final Ref oldRef =
@@ -94,6 +98,11 @@
     return "branch_" + nameRule.getMethodName();
   }
 
+  @Before
+  public void setup() {
+    when(projectsFilter.matches(anyString())).thenReturn(true);
+  }
+
   @SuppressWarnings("deprecation")
   private void setMockRequiredReturnValues() throws IOException {
 
@@ -170,6 +179,7 @@
                 validationMetrics,
                 new DefaultSharedRefEnforcement(),
                 new DummyLockWrapper(),
+                projectsFilter,
                 projectName,
                 refDb);
           }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
index 9e75c8f..029a4c6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
@@ -16,11 +16,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.Factory;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
@@ -33,6 +36,7 @@
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -49,6 +53,7 @@
   @Mock SharedRefDatabaseWrapper sharedRefDb;
   @Mock ValidationMetrics validationMetrics;
   @Mock RefDatabase refDb;
+  @Mock ProjectsFilter projectsFilter;
 
   private final Ref oldRef =
       new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, A_TEST_REF_NAME, AN_OBJECT_ID_1);
@@ -62,6 +67,11 @@
     return "branch_" + nameRule.getMethodName();
   }
 
+  @Before
+  public void setup() {
+    when(projectsFilter.matches(anyString())).thenReturn(true);
+  }
+
   @Test
   public void newUpdateShouldValidateAndSucceed() throws Exception {
 
@@ -92,7 +102,7 @@
   }
 
   @Test
-  public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException {
+  public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() {
 
     doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
@@ -106,8 +116,7 @@
   }
 
   @Test
-  public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate()
-      throws IOException {
+  public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate() {
 
     doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(false)
@@ -124,8 +133,7 @@
   }
 
   @Test
-  public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate()
-      throws IOException {
+  public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate() {
 
     doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(false)
@@ -159,7 +167,7 @@
   }
 
   @Test
-  public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException {
+  public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() {
 
     doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
@@ -184,6 +192,7 @@
                     validationMetrics,
                     new DefaultSharedRefEnforcement(),
                     new DummyLockWrapper(),
+                    projectsFilter,
                     projectName,
                     refDb);
             return RefUpdateValidator;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
index 2eefb5a..d437a73 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -26,6 +26,7 @@
 
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.project.ProjectConfig;
@@ -33,6 +34,7 @@
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
@@ -63,6 +65,7 @@
   @Mock SharedRefDatabaseWrapper sharedRefDb;
   @Mock GitReferenceUpdated gitReferenceUpdated;
   @Mock ProjectVersionLogger verLogger;
+  @Mock ProjectsFilter projectsFilter;
 
   @Inject private ProjectConfig.Factory projectConfigFactory;
   @Inject private InMemoryRepositoryManager repoManager;
@@ -73,6 +76,7 @@
 
   @Before
   public void setUp() throws Exception {
+    when(projectsFilter.matches(any(Event.class))).thenReturn(true);
     InMemoryRepository inMemoryRepo = repoManager.createRepository(A_TEST_PROJECT_NAME_KEY);
     project = projectConfigFactory.create(A_TEST_PROJECT_NAME_KEY);
     project.load(inMemoryRepo);
@@ -105,7 +109,8 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
@@ -150,7 +155,8 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
@@ -190,7 +196,8 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
@@ -227,7 +234,8 @@
     when(refUpdatedEvent.getRefName()).thenReturn(magicRefName);
     repo.branch(magicRefName).commit().create();
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
@@ -242,7 +250,30 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(Project.nameKey("aNonExistentProject"));
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+    assertThat(ref).isNull();
+
+    verifyZeroInteractions(verLogger);
+  }
+
+  @Test
+  public void shouldNotUpdateProjectVersionWhenProjectFilteredOut() throws Exception {
+    when(projectsFilter.matches(any(Event.class))).thenReturn(false);
+
+    Context.setForwardedEvent(false);
+
+    Thread.sleep(1000L);
+    repo.branch("master").commit().create();
+
+    Thread.sleep(1000L);
+    repo.branch("master").update(masterCommit);
+
+    new ProjectVersionRefUpdate(
+            repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
     Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
@@ -257,7 +288,8 @@
         .thenReturn(Optional.of("123"));
 
     Optional<Long> version =
-        new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        new ProjectVersionRefUpdate(
+                repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
             .getProjectRemoteVersion(A_TEST_PROJECT_NAME);
 
     assertThat(version.isPresent()).isTrue();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
index 323db63..b9b07bd 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.never;
@@ -23,9 +24,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.google.gerrit.entities.Project;
+import com.googlesource.gerrit.plugins.multisite.ProjectsFilter;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.OneParameterFunction;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
@@ -47,12 +49,16 @@
 
   @Mock SharedRefDatabaseWrapper sharedRefDb;
 
+  @Mock SharedRefLogger sharedRefLogger;
+
   @Mock RefDatabase localRefDb;
 
   @Mock ValidationMetrics validationMetrics;
 
   @Mock RefUpdate refUpdate;
 
+  @Mock ProjectsFilter projectsFilter;
+
   @Mock OneParameterFunction<ObjectId, Result> rollbackFunction;
 
   String refName;
@@ -69,21 +75,26 @@
     newUpdateRef = newRef(refName, AN_OBJECT_ID_2);
     localRef = newRef(refName, AN_OBJECT_ID_3);
 
-    doReturn(localRef).when(localRefDb).getRef(refName);
+    doReturn(localRef).when(localRefDb).findRef(refName);
     doReturn(localRef).when(localRefDb).exactRef(refName);
     doReturn(newUpdateRef.getObjectId()).when(refUpdate).getNewObjectId();
     doReturn(refName).when(refUpdate).getName();
     lenient().doReturn(oldUpdateRef.getObjectId()).when(refUpdate).getOldObjectId();
     doReturn(Result.FAST_FORWARD).when(rollbackFunction).invoke(any());
 
-    refUpdateValidator =
-        new RefUpdateValidator(
-            sharedRefDb,
-            validationMetrics,
-            defaultRefEnforcement,
-            new DummyLockWrapper(),
-            A_TEST_PROJECT_NAME,
-            localRefDb);
+    doReturn(true).when(projectsFilter).matches(anyString());
+
+    refUpdateValidator = newRefUpdateValidator(sharedRefDb);
+  }
+
+  @Test
+  public void validationShouldSucceedWhenSharedRefDbIsNoop() throws Exception {
+    SharedRefDatabaseWrapper noopSharedRefDbWrapper = new SharedRefDatabaseWrapper(sharedRefLogger);
+
+    Result result =
+        newRefUpdateValidator(noopSharedRefDbWrapper)
+            .executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
+    assertThat(result).isEqualTo(RefUpdate.Result.NEW);
   }
 
   @Test
@@ -119,7 +130,7 @@
     doReturn(true)
         .when(sharedRefDb)
         .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, ObjectId.zeroId());
-    doReturn(localRef).doReturn(null).when(localRefDb).getRef(refName);
+    doReturn(localRef).doReturn(null).when(localRefDb).findRef(refName);
 
     Result result =
         refUpdateValidator.executeRefUpdate(
@@ -140,7 +151,7 @@
     doReturn(true)
         .when(sharedRefDb)
         .compareAndPut(A_TEST_PROJECT_NAME_KEY, localNullRef, newUpdateRef.getObjectId());
-    doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).getRef(refName);
+    doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).findRef(refName);
 
     Result result =
         refUpdateValidator.executeRefUpdate(
@@ -206,20 +217,25 @@
   }
 
   @Test
-  public void shouldRollbackRefUpdateWhenCompareAndPutIsFailing() throws Exception {
-    lenient()
-        .doReturn(false)
-        .when(sharedRefDb)
-        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
+  public void shouldNotUpdateSharedRefDbWhenProjectIsLocal() throws Exception {
+    when(projectsFilter.matches(anyString())).thenReturn(false);
 
-    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
-        .thenThrow(GlobalRefDbSystemError.class);
-    when(rollbackFunction.invoke(any())).thenReturn(Result.LOCK_FAILURE);
+    refUpdateValidator.executeRefUpdate(
+        refUpdate, () -> RefUpdate.Result.NEW, this::defaultRollback);
 
-    refUpdateValidator.executeRefUpdate(refUpdate, () -> Result.NEW, rollbackFunction);
+    verify(sharedRefDb, never())
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
+  }
 
-    verify(rollbackFunction, times(1)).invoke(any());
+  private RefUpdateValidator newRefUpdateValidator(SharedRefDatabaseWrapper refDbWrapper) {
+    return new RefUpdateValidator(
+        refDbWrapper,
+        validationMetrics,
+        defaultRefEnforcement,
+        new DummyLockWrapper(),
+        projectsFilter,
+        A_TEST_PROJECT_NAME,
+        localRefDb);
   }
 
   private Result defaultRollback(ObjectId objectId) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
index 7e4593b..f2b57a1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
@@ -68,7 +68,7 @@
   private TestRepository<InMemoryRepository> repo;
 
   @Before
-  public void setUp() throws Exception {
+  public void setupTestRepo() throws Exception {
     InMemoryRepository inMemoryRepo =
         gitRepositoryManager.createRepository(A_TEST_PROJECT_NAME_KEY);
     repo = new TestRepository<>(inMemoryRepo);