Merge branch 'stable-3.12'

* stable-3.12:
  Fix rollback implementation

Change-Id: I60efdbe7726025136decdef792f7543aace16829
diff --git a/bindings.md b/bindings.md
index c936c5d..72b9190 100644
--- a/bindings.md
+++ b/bindings.md
@@ -34,13 +34,6 @@
     factory(BatchRefUpdateValidator.Factory.class);
     bind(SharedRefDbConfiguration.class).toInstance(cfg.getSharedRefDbConfiguration());
     bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
-    if (cfg.getSharedRefDbConfiguration().getSharedRefDb().getEnforcementRules().isEmpty()) {
-      bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
-    } else {
-      bind(SharedRefEnforcement.class)
-          .to(CustomSharedRefEnforcementByProject.class)
-          .in(Scopes.SINGLETON);
-    }
     DynamicSet.bind(binder(), ExceptionHook.class).to(SharedRefDbExceptionHook.class);
   }
 }
diff --git a/config.md b/config.md
index 21064be..312e84a 100644
--- a/config.md
+++ b/config.md
@@ -11,28 +11,39 @@
 :   Enable the use of a global refdb
     Defaults: true
 
-```ref-database.enforcementRules.<policy>```
-:   Level of consistency enforcement across sites on a project:refs basis.
-    Supports two values for enforcing the policy on multiple projects or refs.
-    If the project or ref is omitted, apply the policy to all projects or all refs.
+```ref-database.storeMutableRefs```
+:   Specifies which projects should have mutable refs stored. An asterisk can be
+    used to match all projects. Excludes draft comments, immutable refs, and
+    cache-automerge refs. An asterisk can be used to match all projects.
 
-    The <policy> can have one of the following values:
+    Defaults: No rules = All projects store mutable refs.
 
-    1. REQUIRED - Throw an exception if a git ref-update is processed against
-    a local ref not yet in sync with the global refdb.
-    The user transaction is cancelled.
+    Details: An asterisk can be used to match all projects. Storage rules are
+    evaluated in the following order: project-specific settings (storeNoRefs, then
+    storeMutableRefs, then storeAllRefs), followed by global settings (using * as
+    a wildcard) in the same order. Each project can only be in one ref storage
+    category.
 
-    2. IGNORED - Ignore any validation against the global refdb.
+```ref-database.storeAllRefs```
+:   Specifies which projects should have all refs stored, including refs which
+    are excluded by default (draft comments, immutable refs, and cache-automerge
+    refs). See ```ref-database.storeMutableRefs``` for more details.
 
-    *Example:*
-    ```
-    [ref-database "enforcementRules"]
-       IGNORED = AProject:/refs/heads/feature
-    ```
+    Details: An asterisk can be used to match all projects. Storage rules are
+    evaluated in the following order: project-specific settings (storeNoRefs, then
+    storeMutableRefs, then storeAllRefs), followed by global settings (using * as
+    a wildcard) in the same order.
 
-    Ignore the alignment with the global refdb for AProject on refs/heads/feature.
+```ref-database.storeNoRefs```
+:   Specifies which projects should not be stored in the global-refdb. No refs
+    from these projects will be stored. An asterisk can be used to match all
+    projects. If a project is in both storeNoRefs and storeAllRefs, it will not
+    be stored; the order of processing is storeNoRefs then storeAllRefs.
 
-    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
+    Details: An asterisk can be used to match all projects. Storage rules are
+    evaluated in the following order: project-specific settings (storeNoRefs, then
+    storeMutableRefs, then storeAllRefs), followed by global settings (using * as
+    a wildcard) in the same order.
 
 ```projects.pattern```
 :   Specifies which projects should be validated against the global refdb.
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
index 4a28687..0e26e1f 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
@@ -14,11 +14,9 @@
 
 package com.gerritforge.gerrit.globalrefdb.validation;
 
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.OutOfSyncException;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.Policy;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
@@ -55,9 +53,7 @@
    *
    * @param sharedRefDb an instance of the global refdb to check for out-of-sync refs.
    * @param validationMetrics to update validation results, such as split-brains.
-   * @param refEnforcement Specific ref enforcements for this project. Either a {@link
-   *     CustomSharedRefEnforcementByProject} when custom policies are provided via configuration *
-   *     file or a {@link DefaultSharedRefEnforcement} for defaults.
+   * @param refEnforcement Specific ref enforcements for this project.
    * @param projectsFilter filter to match whether the project being updated should be validated
    *     against global refdb
    * @param projectName the name of the project being updated.
@@ -94,7 +90,7 @@
    * <ul>
    *   <li>The project being updated is a global project ({@link
    *       RefUpdateValidator#isGlobalProject(String)}
-   *   <li>The enforcement policy for the project being updated is {@link EnforcePolicy#IGNORED}
+   *   <li>The enforcement policy for the project being updated is {@link Policy#EXCLUDE}
    * </ul>
    *
    * @param batchRefUpdate batchRefUpdate object
@@ -109,8 +105,7 @@
       NoParameterVoidFunction batchRefUpdateFunction,
       OneParameterVoidFunction<List<ReceiveCommand>> batchRefUpdateRollbackFunction)
       throws IOException {
-    if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED
-        || !isGlobalProject(projectName)) {
+    if (refEnforcement.getPolicy(projectName) == Policy.EXCLUDE || !isGlobalProject(projectName)) {
       batchRefUpdateFunction.invoke();
       return;
     }
@@ -120,7 +115,7 @@
     } catch (IOException e) {
       logger.atWarning().withCause(e).log(
           "Failed to execute Batch Update on project %s", projectName);
-      if (refEnforcement.getPolicy(projectName) == EnforcePolicy.REQUIRED) {
+      if (refEnforcement.getPolicy(projectName) == Policy.INCLUDE) {
         throw e;
       }
     }
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
index 895a7f4..4009c6e 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidator.java
@@ -17,12 +17,10 @@
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.gerritforge.gerrit.globalrefdb.RefDbLockException;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.OutOfSyncException;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedDbSplitBrainException;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.Policy;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
@@ -89,9 +87,7 @@
    *
    * @param sharedRefDb an instance of the global refdb to check for out-of-sync refs.
    * @param validationMetrics to update validation results, such as split-brains.
-   * @param refEnforcement Specific ref enforcements for this project. Either a {@link
-   *     CustomSharedRefEnforcementByProject} when custom policies are provided via configuration
-   *     file or a {@link DefaultSharedRefEnforcement} for defaults.
+   * @param refEnforcement Whether or not a given ref should be stored
    * @param projectsFilter filter to match whether the project being updated should be validated
    *     against global refdb
    * @param projectName the name of the project being updated.
@@ -131,14 +127,14 @@
    *       RefUpdateValidator#isRefToBeIgnored(String)})
    *   <li>The project being updated is a global project ({@link
    *       RefUpdateValidator#isGlobalProject(String)}
-   *   <li>The enforcement policy for the project being updated is {@link EnforcePolicy#IGNORED}
+   *   <li>The enforcement policy for the project being updated is {@link Policy#EXCLUDE}
    * </ul>
    *
    * @param refUpdate the refUpdate command
    * @param refUpdateFunction the refUpdate function to execute after validation
    * @param rollbackFunction function to invoke when the ref-update needs to be rolled back
    * @return the result of the update, or "null" in case a split brain was detected but the policy
-   *     enforcement was not REQUIRED
+   *     enforcement was not INCLUDE
    * @throws IOException Execution of ref update failed
    */
   public RefUpdate.Result executeRefUpdate(
@@ -148,7 +144,7 @@
       throws IOException {
     if (isRefToBeIgnored(refUpdate.getName())
         || !isGlobalProject(projectName)
-        || refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
+        || refEnforcement.getPolicy(projectName) == Policy.EXCLUDE) {
       return refUpdateFunction.invoke();
     }
 
@@ -162,12 +158,11 @@
     return isRefToBeIgnored;
   }
 
-  private <T extends Throwable> void softFailBasedOnEnforcement(T e, EnforcePolicy policy)
-      throws T {
+  private <T extends Throwable> void softFailBasedOnEnforcement(T e, Policy policy) throws T {
     logger.atWarning().withCause(e).log(
         "Failure while running with policy enforcement %s. Error message: %s",
         policy, e.getMessage());
-    if (policy == EnforcePolicy.REQUIRED) {
+    if (policy == Policy.INCLUDE) {
       throw e;
     }
   }
@@ -215,9 +210,11 @@
   protected void updateSharedDbOrThrowExceptionFor(RefUpdateSnapshot refSnapshot)
       throws IOException {
     // We are not checking refs that should be ignored
-    final EnforcePolicy refEnforcementPolicy =
+    final Policy refEnforcementPolicy =
         refEnforcement.getPolicy(projectName, refSnapshot.getName());
-    if (refEnforcementPolicy == EnforcePolicy.IGNORED) return;
+    if (refEnforcementPolicy == Policy.EXCLUDE) {
+      return;
+    }
 
     boolean succeeded;
     try {
@@ -266,8 +263,8 @@
       RefUpdateSnapshot refUpdateSnapshot, CloseableSet<AutoCloseable> locks)
       throws GlobalRefDbLockException, OutOfSyncException, IOException {
     String refName = refUpdateSnapshot.getName();
-    EnforcePolicy refEnforcementPolicy = refEnforcement.getPolicy(projectName, refName);
-    if (refEnforcementPolicy == EnforcePolicy.IGNORED) {
+    Policy refEnforcementPolicy = refEnforcement.getPolicy(projectName, refName);
+    if (refEnforcementPolicy == Policy.EXCLUDE) {
       return refUpdateSnapshot;
     }
 
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
index 4b7f987..c255491 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbConfiguration.java
@@ -17,13 +17,11 @@
 import static com.google.common.base.Suppliers.memoize;
 import static com.google.common.base.Suppliers.ofInstance;
 
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration.Projects;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration.SharedRefDatabase;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
 import java.io.IOException;
 import java.util.List;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -53,7 +51,17 @@
   public SharedRefDbConfiguration(Config config, String pluginName) {
     Supplier<Config> lazyCfg = lazyLoad(config);
     projects = memoize(() -> new Projects(lazyCfg));
-    sharedRefDb = memoize(() -> new SharedRefDatabase(lazyCfg));
+    sharedRefDb =
+        memoize(
+            () -> {
+              try {
+                return new SharedRefDatabase(lazyCfg);
+              } catch (ConfigInvalidException e) {
+                log.error("Invalid configuration for shared refdb", e);
+                throw new RuntimeException(
+                    "Failed to initialize SharedRefDatabase due to invalid configuration", e);
+              }
+            });
     this.pluginName = pluginName;
   }
 
@@ -99,29 +107,32 @@
 
   /**
    * Represents the global refdb configuration, which is computed by reading the 'ref-database'
-   * section from the configuration file of this library's consumers. It allows to specify whether
-   * it is enabled, specific {@link SharedRefEnforcement}s and to tune other parameters that define
-   * specific behaviours of the global refdb.
+   * section from the configuration file of this library's consumers. It allows specifying whether
+   * it is enabled, storage rules, and to tune other parameters that define specific behaviours of
+   * the global refdb.
    */
   public static class SharedRefDatabase {
     public static final String SECTION = "ref-database";
     public static final String ENABLE_KEY = "enabled";
-    public static final String SUBSECTION_ENFORCEMENT_RULES = "enforcementRules";
+    public static final String STORE_ALL_REFS_KEY = "storeAllRefs";
+    public static final String STORE_MUTABLE_REFS_KEY = "storeMutableRefs";
+    public static final String STORE_NO_REFS_KEY = "storeNoRefs";
     public static final String IGNORED_REFS_PREFIXES = "ignoredRefsPrefixes";
+    public static final String PROJECT = "project";
 
     private final boolean enabled;
-    private final Multimap<EnforcePolicy, String> enforcementRules;
     private final ImmutableSet<String> ignoredRefsPrefixes;
+    private final ImmutableSet<String> storeAllRefs;
+    private final ImmutableSet<String> storeMutableRefs;
+    private final ImmutableSet<String> storeNoRefs;
 
-    private SharedRefDatabase(Supplier<Config> cfg) {
+    private SharedRefDatabase(Supplier<Config> cfg) throws ConfigInvalidException {
       enabled = getBoolean(cfg, SECTION, null, ENABLE_KEY, false);
-      enforcementRules = MultimapBuilder.hashKeys().arrayListValues().build();
-      for (EnforcePolicy policy : EnforcePolicy.values()) {
-        enforcementRules.putAll(
-            policy, getList(cfg, SECTION, SUBSECTION_ENFORCEMENT_RULES, policy.name()));
-      }
-
       ignoredRefsPrefixes = ImmutableSet.copyOf(getList(cfg, SECTION, null, IGNORED_REFS_PREFIXES));
+      storeAllRefs = getSet(cfg, SECTION, STORE_ALL_REFS_KEY, PROJECT);
+      storeMutableRefs = getSet(cfg, SECTION, STORE_MUTABLE_REFS_KEY, PROJECT);
+      storeNoRefs = getSet(cfg, SECTION, STORE_NO_REFS_KEY, PROJECT);
+      validateNoRefStorageOverlap(storeAllRefs, storeMutableRefs, storeNoRefs);
     }
 
     /**
@@ -134,25 +145,30 @@
     }
 
     /**
-     * Getter for the map of {@link EnforcePolicy} to a specific "project:refs". Each entry can be
-     * either be {@link SharedRefEnforcement.EnforcePolicy#IGNORED} or {@link
-     * SharedRefEnforcement.EnforcePolicy#REQUIRED} and it represents the level of consistency
-     * enforcements for that specific "project:refs". If the project or ref is omitted, apply the
-     * policy to all projects or all refs.
+     * Returns the set of projects to store all refs for in the global-refdb
      *
-     * <p>The projec/ref will not be validated against the global refdb if it one to be ignored by
-     * default ({@link SharedRefEnforcement#isRefToBeIgnoredBySharedRefDb(String)} or if it has been
-     * configured so, for example:
-     *
-     * <pre>
-     *     [ref-database "enforcementRules"]
-     *    IGNORED = AProject:/refs/heads/feature
-     * </pre>
-     *
-     * @return Map of "project:refs" policies
+     * @return set of projects to store all refs for
      */
-    public Multimap<EnforcePolicy, String> getEnforcementRules() {
-      return enforcementRules;
+    public ImmutableSet<String> getStoreAllRefs() {
+      return storeAllRefs;
+    }
+
+    /**
+     * Returns the set of projects to store mutable refs for in the global-refdb
+     *
+     * @return set of projects to store only mutable refs for
+     */
+    public ImmutableSet<String> getStoreMutableRefs() {
+      return storeMutableRefs;
+    }
+
+    /**
+     * Returns the set of projects to not store in the global-refdb
+     *
+     * @return set of projects to not store refs for
+     */
+    public ImmutableSet<String> getStoreNoRefs() {
+      return storeNoRefs;
     }
 
     /**
@@ -165,10 +181,57 @@
       return ignoredRefsPrefixes;
     }
 
-    private List<String> getList(
+    private ImmutableList<String> getList(
         Supplier<Config> cfg, String section, String subsection, String name) {
       return ImmutableList.copyOf(cfg.get().getStringList(section, subsection, name));
     }
+
+    private ImmutableSet<String> getSet(
+        Supplier<Config> cfg, String section, String subsection, String name) {
+      return ImmutableSet.copyOf(cfg.get().getStringList(section, subsection, name));
+    }
+
+    /**
+     * Validates that no project is configured in more than one ref storage category.
+     *
+     * @throws ConfigInvalidException if any project appears in multiple categories
+     */
+    private void validateNoRefStorageOverlap(
+        ImmutableSet<String> storeAllRefs,
+        ImmutableSet<String> storeMutableRefs,
+        ImmutableSet<String> storeNoRefs)
+        throws ConfigInvalidException {
+
+      for (String project : storeAllRefs) {
+        if (storeMutableRefs.contains(project)) {
+          throw new ConfigInvalidException(
+              String.format(
+                  "Project '%s' appears in both storeAllRefs and storeMutableRefs. Each project can"
+                      + " only be in one ref storage category (storeMutableRefs, storeAllRefs, or"
+                      + " storeNoRefs).",
+                  project));
+        }
+        if (storeNoRefs.contains(project)) {
+          throw new ConfigInvalidException(
+              String.format(
+                  "Project '%s' appears in both storeAllRefs and storeNoRefs. Each project can only"
+                      + " be in one ref storage category (storeMutableRefs, storeAllRefs, or"
+                      + " storeNoRefs).",
+                  project));
+        }
+      }
+
+      for (String project : storeMutableRefs) {
+        if (storeNoRefs.contains(project)) {
+          throw new ConfigInvalidException(
+              String.format(
+                  "Project '%s' appears in both storeMutableRefs and storeNoRefs. Each project can"
+                      + " only be in one ref storage category (storeMutableRefs, storeAllRefs, or"
+                      + " storeNoRefs).",
+                  project));
+        }
+      }
+    }
   }
 
   /**
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
deleted file mode 100644
index 394aa58..0000000
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
+++ /dev/null
@@ -1,141 +0,0 @@
-// 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.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
-
-import static com.google.common.base.Suppliers.memoize;
-
-import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Splitter;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.Inject;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Implementation of the {@link SharedRefEnforcement} interface which derives project and
- * project/ref enforcement policy from the configuration of the libModule consuming this library
- */
-public class CustomSharedRefEnforcementByProject implements SharedRefEnforcement {
-  private static final String ALL = ".*";
-
-  private final Supplier<Map<String, Map<String, EnforcePolicy>>> predefEnforcements;
-  private final Boolean draftCommentEventsEnabled;
-
-  /**
-   * Constructs a {@code CustomSharedRefEnforcementByProject} with the values specified in the
-   * configuration of the libModule consuming this library
-   *
-   * @param config the libModule configuration
-   */
-  @Inject
-  public CustomSharedRefEnforcementByProject(
-      SharedRefDbConfiguration config,
-      DraftCommentEventsEnabledProvider draftCommentEventsEnabledProvider) {
-    this.predefEnforcements = memoize(() -> parseDryRunEnforcementsToMap(config));
-    this.draftCommentEventsEnabled = draftCommentEventsEnabledProvider.get();
-  }
-
-  private static Map<String, Map<String, EnforcePolicy>> parseDryRunEnforcementsToMap(
-      SharedRefDbConfiguration config) {
-    Map<String, Map<String, EnforcePolicy>> enforcementMap = new HashMap<>();
-
-    for (Map.Entry<EnforcePolicy, String> enforcementEntry :
-        config.getSharedRefDb().getEnforcementRules().entries()) {
-      parseEnforcementEntry(enforcementMap, enforcementEntry);
-    }
-
-    return enforcementMap;
-  }
-
-  private static void parseEnforcementEntry(
-      Map<String, Map<String, EnforcePolicy>> enforcementMap,
-      Map.Entry<EnforcePolicy, String> enforcementEntry) {
-    Iterator<String> projectAndRef = Splitter.on(':').split(enforcementEntry.getValue()).iterator();
-    EnforcePolicy enforcementPolicy = enforcementEntry.getKey();
-
-    if (projectAndRef.hasNext()) {
-      String projectName = emptyToAll(projectAndRef.next());
-      String refName = emptyToAll(projectAndRef.hasNext() ? projectAndRef.next() : ALL);
-
-      Map<String, EnforcePolicy> existingOrDefaultRef =
-          enforcementMap.getOrDefault(projectName, new HashMap<>());
-
-      existingOrDefaultRef.put(refName, enforcementPolicy);
-
-      enforcementMap.put(projectName, existingOrDefaultRef);
-    }
-  }
-
-  private static String emptyToAll(String value) {
-    return value.trim().isEmpty() ? ALL : value;
-  }
-
-  /**
-   * The enforcement policy for 'refName' in 'projectName' as computed from the libModule's
-   * configuration file.
-   *
-   * <p>By default all projects are REQUIRED to be consistent on all refs.
-   *
-   * @param projectName project to be enforced
-   * @param refName ref name to be enforced
-   * @return the enforcement policy for this project/ref
-   */
-  @Override
-  public EnforcePolicy getPolicy(String projectName, String refName) {
-    if (isRefToBeIgnoredBySharedRefDb(refName)) {
-      return EnforcePolicy.IGNORED;
-    }
-
-    return getRefEnforcePolicy(projectName, refName);
-  }
-
-  private EnforcePolicy getRefEnforcePolicy(String projectName, String refName) {
-    Map<String, EnforcePolicy> orDefault =
-        predefEnforcements
-            .get()
-            .getOrDefault(
-                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
-
-    return MoreObjects.firstNonNull(
-        orDefault.getOrDefault(refName, orDefault.get(ALL)), EnforcePolicy.REQUIRED);
-  }
-
-  /**
-   * The enforcement policy for 'projectName' as computed from the libModule's configuration file.
-   *
-   * <p>By default all projects are REQUIRED to be consistent on all refs.
-   *
-   * @param projectName the name of the project to get the policy for
-   * @return the enforcement policy for the project
-   */
-  @Override
-  public EnforcePolicy getPolicy(String projectName) {
-    Map<String, EnforcePolicy> policiesForProject =
-        predefEnforcements
-            .get()
-            .getOrDefault(
-                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
-    return policiesForProject.getOrDefault(ALL, EnforcePolicy.REQUIRED);
-  }
-
-  @Override
-  public Boolean isDraftCommentEventsEnabled() {
-    return draftCommentEventsEnabled;
-  }
-}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java
deleted file mode 100644
index eabd83b..0000000
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcement.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// 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.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
-
-import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.inject.Inject;
-
-/**
- * Default implementation of {@link SharedRefEnforcement}. This class provides the default
- * project/ref enforcement rules when no more specific rules have been configured for the libModule
- * consuming this library.
- */
-public class DefaultSharedRefEnforcement implements SharedRefEnforcement {
-
-  private final Boolean enableDraftCommentEvents;
-
-  @Inject
-  public DefaultSharedRefEnforcement(
-      DraftCommentEventsEnabledProvider draftCommentEventsEnabledProvider) {
-    this.enableDraftCommentEvents = draftCommentEventsEnabledProvider.get();
-  }
-
-  @VisibleForTesting
-  public DefaultSharedRefEnforcement(boolean enableDraftCommentEvents) {
-    this.enableDraftCommentEvents = enableDraftCommentEvents;
-  }
-
-  @VisibleForTesting
-  public DefaultSharedRefEnforcement() {
-    this.enableDraftCommentEvents = false;
-  }
-
-  /**
-   * Returns {@link EnforcePolicy#IGNORED} for refs to be ignored {@link
-   * SharedRefEnforcement#isRefToBeIgnoredBySharedRefDb(String)}, {@link EnforcePolicy#REQUIRED}
-   * otherwise
-   *
-   * @param projectName project to be enforced
-   * @param refName ref name to be enforced
-   * @return the policy for this project/ref
-   */
-  @Override
-  public EnforcePolicy getPolicy(String projectName, String refName) {
-    return isRefToBeIgnoredBySharedRefDb(refName) ? EnforcePolicy.IGNORED : EnforcePolicy.REQUIRED;
-  }
-
-  /**
-   * The global refdb validation policy for 'projectName'
-   *
-   * @param projectName project to be enforced
-   * @return always {@link EnforcePolicy#REQUIRED}
-   */
-  @Override
-  public EnforcePolicy getPolicy(String projectName) {
-    return EnforcePolicy.REQUIRED;
-  }
-
-  @Override
-  public Boolean isDraftCommentEventsEnabled() {
-    return enableDraftCommentEvents;
-  }
-}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
index 405d33a..7a7501c 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcement.java
@@ -14,46 +14,121 @@
 
 package com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
 
+import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.RefNames;
+import com.google.inject.Inject;
 
 /** Type of enforcement to implement between the local and shared RefDb. */
-public interface SharedRefEnforcement {
-  public enum EnforcePolicy {
-    IGNORED,
-    REQUIRED;
+public class SharedRefEnforcement {
+  public enum Policy {
+    EXCLUDE,
+    INCLUDE_MUTABLE,
+    INCLUDE;
+  }
+
+  private final ImmutableSet<String> storeAllRefs;
+  private final ImmutableSet<String> storeMutableRefs;
+  private final ImmutableSet<String> storeNoRefs;
+  private final Boolean enableDraftCommentEvents;
+  private final String ALL = "*";
+
+  @Inject
+  public SharedRefEnforcement(
+      SharedRefDbConfiguration config,
+      DraftCommentEventsEnabledProvider draftCommentEventsEnabledProvider) {
+    this.storeAllRefs = config.getSharedRefDb().getStoreAllRefs();
+    this.storeMutableRefs = config.getSharedRefDb().getStoreMutableRefs();
+    this.storeNoRefs = config.getSharedRefDb().getStoreNoRefs();
+    this.enableDraftCommentEvents = draftCommentEventsEnabledProvider.get();
+  }
+
+  @VisibleForTesting
+  public SharedRefEnforcement(
+      ImmutableSet<String> storeAllRefs,
+      ImmutableSet<String> storeMutableRefs,
+      ImmutableSet<String> storeNoRefs,
+      boolean enableDraftCommentEvents) {
+    this.storeAllRefs = storeAllRefs;
+    this.storeMutableRefs = storeMutableRefs;
+    this.storeNoRefs = storeNoRefs;
+    this.enableDraftCommentEvents = enableDraftCommentEvents;
+  }
+
+  @VisibleForTesting
+  public SharedRefEnforcement() {
+    this.storeAllRefs = ImmutableSet.of();
+    this.storeMutableRefs = ImmutableSet.of();
+    this.storeNoRefs = ImmutableSet.of();
+    this.enableDraftCommentEvents = false;
   }
 
   /**
-   * Get the enforcement policy for a project/refName.
+   * The enforcement policy for 'refName' in 'projectName'
    *
    * @param projectName project to be enforced
    * @param refName ref name to be enforced
-   * @return the {@link EnforcePolicy} value
+   * @return the enforcement policy for this project/ref
    */
-  public EnforcePolicy getPolicy(String projectName, String refName);
+  public Policy getPolicy(String projectName, String refName) {
+    Policy configuredPolicy = getPolicy(projectName);
+    if (configuredPolicy == Policy.INCLUDE_MUTABLE) {
+      return isRefToBeIgnoredBySharedRefDb(refName) ? Policy.EXCLUDE : Policy.INCLUDE;
+    }
+    return configuredPolicy;
+  }
 
   /**
-   * Get the enforcement policy for a project
+   * Returns the configured enforcement policy for a project. First checks the project-specific
+   * settings, then the global projects setting. Priority order is storeNoRefs over storeMutableRefs
+   * and storeAllRefs.
    *
-   * @param projectName the name of the project
-   * @return the {@link EnforcePolicy} value
-   */
-  public EnforcePolicy getPolicy(String projectName);
-
-  /**
-   * Get whether the streaming of draft comments events is enabled.
+   * <p>If no specific policy is configured, defaults to storing mutable refs.
    *
-   * @return true when enabled, false otherwise
+   * @param projectName the name of the project to get the policy for
+   * @return the enforcement policy for the project
    */
-  public Boolean isDraftCommentEventsEnabled();
+  public Policy getPolicy(String projectName) {
+    if (storeNoRefs.contains(projectName)) {
+      return Policy.EXCLUDE;
+    }
+    if (storeMutableRefs.contains(projectName)) {
+      return Policy.INCLUDE_MUTABLE;
+    }
+    if (storeAllRefs.contains(projectName)) {
+      return Policy.INCLUDE;
+    }
+
+    if (storeNoRefs.contains(ALL)) {
+      return Policy.EXCLUDE;
+    }
+    if (storeMutableRefs.contains(ALL)) {
+      return Policy.INCLUDE_MUTABLE;
+    }
+    if (storeAllRefs.contains(ALL)) {
+      return Policy.INCLUDE;
+    }
+    return Policy.INCLUDE_MUTABLE;
+  }
 
   /**
-   * Check if a refName should be ignored by global refdb. The Default behaviour is to ignore:
+   * Returns whether draft comment events are enabled.
+   *
+   * @return true if draft comment events are enabled, false otherwise
+   */
+  public Boolean isDraftCommentEventsEnabled() {
+    return enableDraftCommentEvents;
+  }
+
+  /**
+   * Check if a refName should be ignored by global refdb. These rules apply when not storing all
+   * refs.
    *
    * <ul>
-   *   <li>refs/draft-comments :user-specific temporary storage that does need to be seen by other
-   *       users/sites only when their streaming is enabled via
-   *       event.stream-events.enableDraftCommentEvents.
+   *   <li>refs/draft-comments :user-specific temporary storage that does not need to be seen by
+   *       other users/sites
    *   <li>refs/changes/&lt;non-meta&gt;: those refs are immutable
    *   <li>refs/cache-automerge: these refs would be never replicated anyway
    * </ul>
@@ -61,9 +136,9 @@
    * @param refName the name of the ref to check
    * @return true if ref should be ignored; false otherwise
    */
-  default boolean isRefToBeIgnoredBySharedRefDb(String refName) {
+  boolean isRefToBeIgnoredBySharedRefDb(String refName) {
     return refName == null
-        || (refName.startsWith("refs/draft-comments") && !isDraftCommentEventsEnabled())
+        || (refName.startsWith("refs/draft-comments") && !enableDraftCommentEvents)
         || (refName.startsWith("refs/changes")
             && !refName.endsWith("/meta")
             && !refName.endsWith(RefNames.ROBOT_COMMENTS_SUFFIX))
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
index bb50f74..86ba772 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
@@ -29,9 +29,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator.OneParameterVoidFunction;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.RefFixture;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
 import com.google.common.collect.ImmutableSet;
@@ -108,7 +108,7 @@
     BatchRefUpdateValidator batchRefUpdateValidator =
         getRefValidatorForEnforcement(tmpRefEnforcement);
 
-    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+    doReturn(SharedRefEnforcement.Policy.INCLUDE)
         .when(batchRefUpdateValidator.refEnforcement)
         .getPolicy(A_TEST_PROJECT_NAME, A_REF_NAME_1);
 
@@ -166,9 +166,10 @@
     BatchRefUpdateValidator batchRefUpdateValidator =
         getRefValidatorForEnforcement(tmpRefEnforcement);
 
-    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+    doReturn(SharedRefEnforcement.Policy.INCLUDE)
         .when(batchRefUpdateValidator.refEnforcement)
         .getPolicy(A_TEST_PROJECT_NAME, A_REF_NAME_1);
+
     doReturn(false).when(sharedRefDatabase).isUpToDate(eq(A_TEST_PROJECT_NAME_KEY), any());
     doReturn(true).when(sharedRefDatabase).exists(eq(A_TEST_PROJECT_NAME_KEY), any());
 
@@ -188,9 +189,10 @@
     BatchRefUpdateValidator batchRefUpdateValidator =
         getRefValidatorForEnforcement(tmpRefEnforcement);
 
-    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+    doReturn(SharedRefEnforcement.Policy.INCLUDE)
         .when(batchRefUpdateValidator.refEnforcement)
         .getPolicy(A_TEST_PROJECT_NAME, A_REF_NAME_1);
+
     doReturn(true).when(sharedRefDatabase).isUpToDate(any(), any());
 
     doThrow(TestError.class).when(sharedRefDatabase).compareAndPut(any(), any(), any());
@@ -212,7 +214,7 @@
     BatchRefUpdateValidator batchRefUpdateValidator =
         getRefValidatorForEnforcement(tmpRefEnforcement);
 
-    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+    doReturn(SharedRefEnforcement.Policy.INCLUDE)
         .when(batchRefUpdateValidator.refEnforcement)
         .getPolicy(A_TEST_PROJECT_NAME, A_REF_NAME_1);
 
@@ -257,7 +259,10 @@
   }
 
   private BatchRefUpdateValidator newDefaultValidator() {
-    return getRefValidatorForEnforcement(new DefaultSharedRefEnforcement());
+    return getRefValidatorForEnforcement(
+        new SharedRefEnforcement(
+            new SharedRefDbConfiguration(new Config(), "testplugin"),
+            new DraftCommentEventsEnabledProvider(new Config())));
   }
 
   private BatchRefUpdateValidator getRefValidatorForEnforcement(
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLoggerTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLoggerTest.java
index 5b9a9dd..ea94f19 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLoggerTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/Log4jSharedRefLoggerTest.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.json.OutputFormat;
 import com.google.gerrit.server.Sequence;
+import com.google.gerrit.server.config.LogConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.gson.Gson;
@@ -145,7 +146,9 @@
 
   private Log4jSharedRefLogger newLog4jSharedRefLogger() throws IOException {
     final Log4jSharedRefLogger log4jSharedRefLogger =
-        new Log4jSharedRefLogger(new SystemLog(new SitePaths(newPath()), baseConfig), repoManager);
+        new Log4jSharedRefLogger(
+            new SystemLog(new SitePaths(newPath()), baseConfig, new LogConfig(baseConfig)),
+            repoManager);
     log4jSharedRefLogger.setLogger(logWriterLogger());
     return log4jSharedRefLogger;
   }
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
index 01018f2..496e1d8 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
@@ -25,14 +25,16 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.gerritforge.gerrit.globalrefdb.RefDbLockException;
 import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator.OneParameterFunction;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.RefFixture;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Project;
 import java.io.IOException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
@@ -47,8 +49,10 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class RefUpdateValidatorTest implements RefFixture {
-  private static final DefaultSharedRefEnforcement defaultRefEnforcement =
-      new DefaultSharedRefEnforcement();
+  private static final SharedRefEnforcement defaultRefEnforcement =
+      new SharedRefEnforcement(
+          new SharedRefDbConfiguration(new Config(), "testplugin"),
+          new DraftCommentEventsEnabledProvider(new Config()));
 
   @Mock SharedRefDatabaseWrapper sharedRefDb;
 
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
index 4c6290d..92790ea 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
@@ -27,13 +27,15 @@
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
+import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
 import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.RefFixture;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -180,7 +182,9 @@
             return new BatchRefUpdateValidator(
                 sharedRefDb,
                 validationMetrics,
-                new DefaultSharedRefEnforcement(),
+                new SharedRefEnforcement(
+                    new SharedRefDbConfiguration(new Config(), "testplugin"),
+                    new DraftCommentEventsEnabledProvider(new Config())),
                 projectsFilter,
                 projectName,
                 refDb,
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
index 1547bd3..2330df2 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
@@ -14,8 +14,6 @@
 
 package com.gerritforge.gerrit.globalrefdb.validation;
 
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.TestPlugin;
@@ -104,7 +102,6 @@
       bind(ValidationMetrics.class);
 
       bind(SharedRefDbGitRepositoryManager.class);
-      bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
     }
   }
 }
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
deleted file mode 100644
index a21ff3e..0000000
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration.SharedRefDatabase;
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import java.util.Arrays;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Before;
-import org.junit.Test;
-
-public class CustomSharedRefEnforcementByProjectTest implements RefFixture {
-
-  SharedRefEnforcement refEnforcement;
-
-  @Before
-  public void setUp() {
-    Config sharedRefDbConfig = new Config();
-    sharedRefDbConfig.setStringList(
-        SharedRefDatabase.SECTION,
-        SharedRefDatabase.SUBSECTION_ENFORCEMENT_RULES,
-        EnforcePolicy.IGNORED.name(),
-        Arrays.asList(
-            "ProjectOne",
-            "ProjectTwo:refs/heads/master/test",
-            "ProjectTwo:refs/heads/master/test2"));
-
-    refEnforcement = newCustomRefEnforcement(sharedRefDbConfig);
-  }
-
-  @Test
-  public void projectOneShouldReturnDesiredForAllRefs() {
-    Ref aRef = newRef("refs/heads/master/2", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void projectOneEnforcementShouldAlwaysPrevail() {
-    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aNonListedProjectShouldRequireRefForMasterTest() {
-    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", aRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void projectTwoSpecificRefShouldReturnIgnoredPolicy() {
-    Ref refOne = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
-    Ref refTwo = newRef("refs/heads/master/test2", AN_OBJECT_ID_1);
-
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refTwo.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aNonListedProjectShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/newChange", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aNonListedRefInProjectShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aNonListedProjectAndRefShouldReturnRequired() {
-    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForProjectOneShouldReturnIgnored() {
-    assertThat(refEnforcement.getPolicy("ProjectOne")).isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void getProjectPolicyForProjectTwoShouldReturnRequired() {
-    assertThat(refEnforcement.getPolicy("ProjectTwo")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForNonListedProjectShouldReturnRequired() {
-    assertThat(refEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getProjectPolicyForNonListedProjectWhenSingleProject() {
-    SharedRefEnforcement customEnforcement =
-        newCustomRefEnforcementWithValue(EnforcePolicy.IGNORED, ":refs/heads/master");
-
-    assertThat(customEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void getANonListedProjectWhenOnlyOneProjectIsListedShouldReturnRequired() {
-    SharedRefEnforcement customEnforcement =
-        newCustomRefEnforcementWithValue(EnforcePolicy.IGNORED, "AProject:");
-    assertThat(customEnforcement.getPolicy("NonListedProject", "refs/heads/master"))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  private SharedRefEnforcement newCustomRefEnforcementWithValue(
-      EnforcePolicy policy, String... projectAndRefs) {
-    Config sharedRefDbConfiguration = new Config();
-    sharedRefDbConfiguration.setStringList(
-        SharedRefDatabase.SECTION,
-        SharedRefDatabase.SUBSECTION_ENFORCEMENT_RULES,
-        policy.name(),
-        Arrays.asList(projectAndRefs));
-    return newCustomRefEnforcement(sharedRefDbConfiguration);
-  }
-
-  private SharedRefEnforcement newCustomRefEnforcement(Config sharedRefDbConfig) {
-    return new CustomSharedRefEnforcementByProject(
-        new SharedRefDbConfiguration(sharedRefDbConfig, "testplugin"),
-        new DraftCommentEventsEnabledProvider(new Config()));
-  }
-
-  @Override
-  public String testBranch() {
-    return "fooBranch";
-  }
-}
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
deleted file mode 100644
index f26846c..0000000
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
-import com.google.gerrit.entities.RefNames;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Test;
-
-public class DefaultSharedRefEnforcementTest implements RefFixture {
-
-  SharedRefEnforcement refEnforcement = new DefaultSharedRefEnforcement();
-
-  @Test
-  public void anImmutableChangeShouldBeIgnored() {
-    Ref immutableChangeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aChangeMetaShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/changes/01/1/meta", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aChangeRobotCommentsShouldNotBeIgnored() {
-    Ref robotCommentsMutableRef =
-        newRef("refs/changes/01/1" + RefNames.ROBOT_COMMENTS_SUFFIX, AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, robotCommentsMutableRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void aCacheAutomergeShouldBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/cache-automerge/01/1/1000000", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void aDraftCommentsShouldBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.IGNORED);
-  }
-
-  @Test
-  public void regularRefHeadsMasterShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/heads/master", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void regularCommitShouldNotBeIgnored() {
-    Ref immutableChangeRef = newRef("refs/heads/stable-2.16", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void allUsersExternalIdsRefShouldBeRequired() {
-    Ref refOne = newRef("refs/meta/external-ids", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy("All-Users", refOne.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Test
-  public void draftCommentsShouldBeRequiredWhenDraftCommentEventsEnabled() {
-    SharedRefEnforcement refEnforcement = new DefaultSharedRefEnforcement(true);
-
-    Ref draftCommentRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
-    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, draftCommentRef.getName()))
-        .isEqualTo(EnforcePolicy.REQUIRED);
-  }
-
-  @Override
-  public String testBranch() {
-    return "fooBranch";
-  }
-}
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcementTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcementTest.java
new file mode 100644
index 0000000..3f5136b
--- /dev/null
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcementTest.java
@@ -0,0 +1,236 @@
+// Copyright (C) 2024 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.gerritforge.gerrit.globalrefdb.validation.dfsrefdb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.gerritforge.gerrit.globalrefdb.DraftCommentEventsEnabledProvider;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration.SharedRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement.Policy;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.RefNames;
+import java.util.Arrays;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SharedRefEnforcementTest implements RefFixture {
+
+  SharedRefEnforcement refEnforcement;
+
+  @Before
+  public void setUp() {
+    Config sharedRefDbConfig = new Config();
+    refEnforcement = newRefEnforcement(sharedRefDbConfig);
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void expectExceptionIfProjectStoredInMultipleConfigurations() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_NO_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_ALL_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+
+    SharedRefEnforcement refEnforcement = newRefEnforcement(sharedRefDbConfig);
+  }
+
+  @Test
+  public void shouldProcessStoreNoRefsBeforeStoreAllRefs() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_NO_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_ALL_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList("*"));
+
+    SharedRefEnforcement refEnforcement = newRefEnforcement(sharedRefDbConfig);
+    Ref changeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.EXCLUDE);
+  }
+
+  @Test
+  public void shouldNotIncludePatchSetRefWhenStoringNoRefs() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_NO_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+
+    SharedRefEnforcement refEnforcement = newRefEnforcement(sharedRefDbConfig);
+    Ref changeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.EXCLUDE);
+  }
+
+  @Test
+  public void shouldIncludePatchSetRefWhenStoringAllRefs() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_ALL_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList("*"));
+
+    SharedRefEnforcement refEnforcementIncludeAll = newRefEnforcement(sharedRefDbConfig);
+
+    Ref immutableChangeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+    Ref draftCommentRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
+    Ref cacheAutomergeRef = newRef("refs/cache-automerge/01/1/1000000", AN_OBJECT_ID_1);
+
+    assertThat(
+            refEnforcementIncludeAll.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.INCLUDE);
+    assertThat(refEnforcementIncludeAll.getPolicy(A_TEST_PROJECT_NAME, draftCommentRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.INCLUDE);
+    assertThat(refEnforcementIncludeAll.getPolicy(A_TEST_PROJECT_NAME, cacheAutomergeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.INCLUDE);
+  }
+
+  @Test
+  public void shouldPrioritizeSpecificProjectConfigurationOverUsingWildcard() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_NO_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList("*"));
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_ALL_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+
+    SharedRefEnforcement refEnforcement = newRefEnforcement(sharedRefDbConfig);
+    Ref changeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.INCLUDE);
+  }
+
+  @Test
+  public void shouldExcludePatchSetRefWhenStoringMutableRefs() {
+    Config sharedRefDbConfig = new Config();
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_MUTABLE_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList(A_TEST_PROJECT_NAME));
+    sharedRefDbConfig.setStringList(
+        SharedRefDatabase.SECTION,
+        SharedRefDatabase.STORE_ALL_REFS_KEY,
+        SharedRefDatabase.PROJECT,
+        Arrays.asList("*"));
+
+    SharedRefEnforcement refEnforcement = newRefEnforcement(sharedRefDbConfig);
+    Ref changeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(SharedRefEnforcement.Policy.EXCLUDE);
+  }
+
+  @Test
+  public void patchSetRefIsExcludedByDefault() {
+    Ref changeRef = newRef(A_REF_NAME_OF_A_PATCHSET, AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(Policy.EXCLUDE);
+  }
+
+  @Test
+  public void changeMetaRefIncludedByDefault() {
+    Ref changeRef = newRef("refs/changes/01/1/meta", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, changeRef.getName()))
+        .isEqualTo(Policy.INCLUDE);
+  }
+
+  @Test
+  public void changeRobotCommentsIncludedByDefault() {
+    Ref robotCommentsMutableRef =
+        newRef("refs/changes/01/1" + RefNames.ROBOT_COMMENTS_SUFFIX, AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, robotCommentsMutableRef.getName()))
+        .isEqualTo(Policy.INCLUDE);
+  }
+
+  @Test
+  public void cacheAutomergeExcludedByDefault() {
+    Ref immutableChangeRef = newRef("refs/cache-automerge/01/1/1000000", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(Policy.EXCLUDE);
+  }
+
+  @Test
+  public void draftCommentExcludedByDefault() {
+    Ref immutableChangeRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(Policy.EXCLUDE);
+  }
+
+  @Test
+  public void regularRefHeadsMasterIncludedByDefault() {
+    Ref immutableChangeRef = newRef("refs/heads/master", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(Policy.INCLUDE);
+  }
+
+  @Test
+  public void regularCommitIncludedByDefault() {
+    Ref immutableChangeRef = newRef("refs/heads/stable-2.16", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(Policy.INCLUDE);
+  }
+
+  @Test
+  public void allUsersExternalIdsRefIncludedByDefault() {
+    Ref refOne = newRef("refs/meta/external-ids", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("All-Users", refOne.getName())).isEqualTo(Policy.INCLUDE);
+  }
+
+  @Test
+  public void draftCommentsIncludedWhenDraftCommentEventsEnabled() {
+    SharedRefEnforcement refEnforcement =
+        new SharedRefEnforcement(ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of(), true);
+    Ref draftCommentRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, draftCommentRef.getName()))
+        .isEqualTo(Policy.INCLUDE);
+  }
+
+  private SharedRefEnforcement newRefEnforcement(Config sharedRefDbConfig) {
+    return new SharedRefEnforcement(
+        new SharedRefDbConfiguration(sharedRefDbConfig, "testplugin"),
+        new DraftCommentEventsEnabledProvider(sharedRefDbConfig));
+  }
+
+  @Override
+  public String testBranch() {
+    return "fooBranch";
+  }
+}