Use refs/meta/config/OWNERS as global default

Allow to have project global default for the OWNERS file configuration
stored under refs/meta/config.

Enable the definition of global policies, regardless of the branch
name, to owners of specific pattern-matching files.

Change-Id: Iae5c73e902f155dd264227eb866474d7805b9016
diff --git a/config.md b/config.md
index 99337ad..59d4a9c 100644
--- a/config.md
+++ b/config.md
@@ -63,6 +63,26 @@
 If for each patch there is a reviewer who gave a Code-Review +2 then the plugin
 will not add any labels, otherwise, it will add ```label('Code-Review from owners', need(_)).```
 
+## Global project OWNERS
+
+Set a OWNERS file into the project refs/meta/config to define a global set of
+rules applied to every change pushed, regardless of the folder or target branch.
+
+Example of assigning every configuration files to a specific owner group:
+```yaml
+matchers:
+- suffix: *.config
+  owners:
+  - Configuration Managers
+```
+
+Global refs/meta/config OWNERS configuration is inherited only when the OWNERS file
+contain the 'inherited: true' condition at the top of the file or if they are absent.
+
+That means that in the absence of any OWNERS file in the target branch, the refs/meta/config
+OWNERS is used as global default.
+
+
 ## Example 1 - OWNERS file without matchers and default Gerrit submit rules
 
 Given an OWNERS configuration of:
diff --git a/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwners.java b/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwners.java
index 17d6fdd..8413704 100644
--- a/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwners.java
+++ b/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwners.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Account.Id;
 import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListEntry;
 
@@ -100,6 +101,12 @@
     OwnersMap ownersMap = new OwnersMap();
     try {
       String rootPath = "OWNERS";
+
+      PathOwnersEntry projectEntry =
+          getOwnersConfig(rootPath, RefNames.REFS_CONFIG)
+              .map(conf -> new PathOwnersEntry(rootPath, conf, accounts, Collections.emptySet()))
+              .orElse(new PathOwnersEntry());
+
       PathOwnersEntry rootEntry =
           getOwnersConfig(rootPath, branch).map(
               conf -> new PathOwnersEntry(rootPath, conf, accounts, Collections
@@ -109,7 +116,7 @@
       Map<String, PathOwnersEntry> entries = new HashMap<>();
       PathOwnersEntry currentEntry = null;
       for (String path : modifiedPaths) {
-        currentEntry = resolvePathEntry(path, branch, rootEntry, entries);
+        currentEntry = resolvePathEntry(path, branch, projectEntry, rootEntry, entries);
 
         // add owners to file for matcher predicates
         ownersMap.addFileOwners(path,currentEntry.getOwners());
@@ -155,12 +162,32 @@
   }
 
   private PathOwnersEntry resolvePathEntry(
-      String path, String branch, PathOwnersEntry rootEntry, Map<String, PathOwnersEntry> entries)
+      String path,
+      String branch,
+      PathOwnersEntry projectEntry,
+      PathOwnersEntry rootEntry,
+      Map<String, PathOwnersEntry> entries)
       throws IOException {
     String[] parts = path.split("/");
     PathOwnersEntry currentEntry = rootEntry;
     Set<Id> currentOwners = currentEntry.getOwners();
     StringBuilder builder = new StringBuilder();
+
+    if (rootEntry.isInherited()) {
+      for(Matcher matcher : projectEntry.getMatchers().values()) {
+        if(!currentEntry.hasMatcher(matcher.getPath())) {
+          currentEntry.addMatcher(matcher);
+        }
+      }
+      if (currentEntry.getOwners().isEmpty()) {
+        currentEntry.setOwners(projectEntry.getOwners());
+        currentOwners = currentEntry.getOwners();
+      }
+      if (currentEntry.getOwnersPath() == null) {
+        currentEntry.setOwnersPath(projectEntry.getOwnersPath());
+      }
+    }
+
     // Iterate through the parent paths, not including the file name
     // itself
     for (int i = 0; i < parts.length - 1; i++) {
@@ -174,9 +201,9 @@
       } else {
         String ownersPath = partial + "OWNERS";
         Optional<OwnersConfig> conf = getOwnersConfig(ownersPath, branch);
+        final Set<Id> owners = currentOwners;
         currentEntry =
-            conf.map(
-                c -> new PathOwnersEntry(ownersPath, c, accounts, currentOwners))
+            conf.map(c -> new PathOwnersEntry(ownersPath, c, accounts, owners))
                 .orElse(currentEntry);
         if (conf.map(OwnersConfig::isInherited).orElse(false)) {
           for (Matcher m : currentEntry.getMatchers().values()) {
diff --git a/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwnersEntry.java b/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwnersEntry.java
index 407584e..c56d5fd 100644
--- a/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwnersEntry.java
+++ b/owners-common/src/main/java/com/vmware/gerrit/owners/common/PathOwnersEntry.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
 
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -31,8 +32,10 @@
  * specific path.
  */
 class PathOwnersEntry {
+  private final boolean inherited;
 
   public PathOwnersEntry() {
+    inherited = true;
   }
 
   public PathOwnersEntry(String path, OwnersConfig config, Accounts accounts,
@@ -45,6 +48,7 @@
       this.owners.addAll(inheritedOwners);
     }
     this.matchers = config.getMatchers();
+    this.inherited = config.isInherited();
   }
 
   @Override
@@ -86,4 +90,18 @@
   public void setMatchers(Map<String, Matcher> matchers) {
     this.matchers = matchers;
   }
+
+  public boolean isInherited() {
+    return inherited;
+  }
+
+  public void addMatchers(Collection<Matcher> values) {
+    for (Matcher matcher : values) {
+      addMatcher(matcher);
+    }
+  }
+
+  public boolean hasMatcher(String path) {
+    return this.matchers.containsKey(path);
+  }
 }
diff --git a/owners-common/src/test/java/com/vmware/gerrit/owners/common/Config.java b/owners-common/src/test/java/com/vmware/gerrit/owners/common/Config.java
index 8048391..148e116 100644
--- a/owners-common/src/test/java/com/vmware/gerrit/owners/common/Config.java
+++ b/owners-common/src/test/java/com/vmware/gerrit/owners/common/Config.java
@@ -30,7 +30,6 @@
 
 import com.google.common.base.Charsets;
 import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListEntry;