Allow reviewer filter configuration without merging

This adds the global plugin configuration "mergeFilters", allowing
the configuration of reviewer filters without merging, e.g:

Project B is a child of Project A.
Project A defines the following filter:
	[filter "*"]
		reviewer = GroupA
Project B defines the following filter:
	[filter "*"]
		reviewer = GroupB

With mergeFilters = true (which is the default value, keeping current
behaviour), both GroupA and GroupB would be added as reviewers. With
mergeFilters = false, only GroupB would be added as reviewers.

Change-Id: I45a531f8c3718fdb991428ae470390a3c5c860d6
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/FiltersFactory.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/FiltersFactory.java
index 9bdf676..c125289 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/FiltersFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/FiltersFactory.java
@@ -31,22 +31,29 @@
 
   private final PluginConfigFactory configFactory;
   private final ReviewerFilterCollection.Factory filterCollectionFactory;
+  private final GlobalConfig globalConfig;
   private final String pluginName;
 
   @Inject
   public FiltersFactory(
       PluginConfigFactory configFactory,
       ReviewerFilterCollection.Factory filterCollectionFactory,
+      GlobalConfig globalConfig,
       @PluginName String pluginName) {
     this.configFactory = configFactory;
     this.filterCollectionFactory = filterCollectionFactory;
+    this.globalConfig = globalConfig;
     this.pluginName = pluginName;
   }
 
   public List<ReviewerFilter> withInheritance(Project.NameKey projectName) {
     Config cfg;
     try {
-      cfg = configFactory.getProjectPluginConfigWithMergedInheritance(projectName, pluginName);
+      if (globalConfig.mergeFilters()) {
+        cfg = configFactory.getProjectPluginConfigWithMergedInheritance(projectName, pluginName);
+      } else {
+        cfg = configFactory.getProjectPluginConfigWithInheritance(projectName, pluginName);
+      }
     } catch (NoSuchProjectException e) {
       logger.atSevere().log("Unable to get config for project %s", projectName.get());
       cfg = new Config();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/GlobalConfig.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/GlobalConfig.java
index b61279f..cd0354f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/GlobalConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/GlobalConfig.java
@@ -26,10 +26,12 @@
   private static final String KEY_ENABLE_REST = "enableREST";
   private static final String KEY_SUGGEST_ONLY = "suggestOnly";
   private static final String KEY_IGNORE_WIP = "ignoreWip";
+  private static final String KEY_MERGE_FILTERS = "mergeFilters";
 
   private final boolean enableREST;
   private final boolean suggestOnly;
   private final boolean ignoreWip;
+  private final boolean mergeFilters;
 
   @Inject
   GlobalConfig(PluginConfigFactory cfgFactory, @PluginName String pluginName) {
@@ -37,6 +39,7 @@
     this.enableREST = cfg.getBoolean(pluginName, null, KEY_ENABLE_REST, true);
     this.suggestOnly = cfg.getBoolean(pluginName, null, KEY_SUGGEST_ONLY, false);
     this.ignoreWip = cfg.getBoolean(pluginName, null, KEY_IGNORE_WIP, true);
+    this.mergeFilters = cfg.getBoolean(pluginName, null, KEY_MERGE_FILTERS, true);
   }
 
   public boolean enableREST() {
@@ -50,4 +53,8 @@
   public boolean ignoreWip() {
     return ignoreWip;
   }
+
+  public boolean mergeFilters() {
+    return mergeFilters;
+  }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 173f51a..5c375d4 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -9,6 +9,7 @@
     enableREST = true
     suggestOnly = false
     ignoreWip = false
+    mergeFilters = true
 ```
 
 reviewers.enableREST
@@ -28,6 +29,12 @@
 	considered when adding reviewers. Defaults to true. To enable adding
 	reviewers on changes in WIP state set this value to false.
 
+reviewers.mergeFilters
+:	Decides whether to merge the filter-sections from parent projects or not.
+	If set to false	filter-sections in child project will override
+	filter-sections, with identical filters, in the parent project(s).
+	Defaults to true.
+
 Per project configuration of the @PLUGIN@ plugin is done in the
 `reviewers.config` file of the project. Missing values are inherited
 from the parent projects. This means a global default configuration can
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
index 64ac429..65e72e5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
@@ -24,14 +24,20 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GlobalPluginConfig;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewerInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import java.util.Set;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Test;
 
 @NoHttpd
@@ -155,6 +161,20 @@
     assertThat(gApi.changes().id(changeId).get().reviewers.get(CC)).isNull();
   }
 
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(pluginName = "reviewers", name = "reviewers.mergeFilters", value = "false")
+  public void reviewersOverriddenWithMergeFiltersFalse() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+    Project.NameKey childProject = projectOperations.newProject().parent(project).create();
+    TestRepository<?> metaConfig = checkoutRefsMetaConfig(cloneProject(childProject));
+    createFiltersFor(metaConfig, filter("*").reviewer(user2));
+    createFilters(filter("*").reviewer(user));
+    TestRepository<InMemoryRepository> childRepo = cloneProject(childProject);
+    String changeId = createChange(childRepo).getChangeId();
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user2.id()));
+  }
+
   private void addReviewer(String changeId, TestAccount user, ReviewerState state)
       throws Exception {
     ReviewerInput input = new ReviewerInput();