Prioritize per-project over global ref storage settings

Priority order is storeNoRefs -> storeMutableRefs -> storeAllRefs

Change-Id: Ib32f61e942734b9d190213a632105dccba8bc502
diff --git a/config.md b/config.md
index c60f7a9..7f6a45f 100644
--- a/config.md
+++ b/config.md
@@ -45,17 +45,32 @@
 
     Defaults: No rules = All projects store mutable 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.
+
 ```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.
 
+    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.
+
 ```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.
 
+    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.
     It can be provided more than once, and supports three formats: regular
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 3dcaeec..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
@@ -81,20 +81,33 @@
   }
 
   /**
-   * The enforcement policy for 'projectName'. By default all projects are INCLUDE to be consistent
-   * on all refs.
+   * 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.
+   *
+   * <p>If no specific policy is configured, defaults to storing mutable refs.
    *
    * @param projectName the name of the project to get the policy for
    * @return the enforcement policy for the project
    */
   public Policy getPolicy(String projectName) {
-    if (storeMutableRefs.contains(ALL) || storeMutableRefs.contains(projectName)) {
-      return Policy.INCLUDE_MUTABLE;
-    }
-    if (storeNoRefs.contains(ALL) || storeNoRefs.contains(projectName)) {
+    if (storeNoRefs.contains(projectName)) {
       return Policy.EXCLUDE;
     }
-    if (storeAllRefs.contains(ALL) || storeAllRefs.contains(projectName)) {
+    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;
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
index 4618425..fae50b2 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcementTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/dfsrefdb/SharedRefEnforcementTest.java
@@ -100,6 +100,27 @@
   }
 
   @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(