Merge branch 'stable-3.10'

* stable-3.10:
  Add autoAssignField to docs
  Restore the ability for global definition of the owners' label

Change-Id: I731dbb25fd3c6c74bdcac7b57bd590bed69b1710
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
index 9a8edb6..16c720a 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
@@ -90,6 +90,7 @@
   private final AutoAssignConfig cfg;
 
   private final PathOwnersEntriesCache cache;
+  private final PluginSettings pluginSettings;
 
   @Inject
   public GitRefListener(
@@ -103,7 +104,8 @@
       Provider<CurrentUser> currentUserProvider,
       ChangeNotes.Factory notesFactory,
       AutoAssignConfig cfg,
-      PathOwnersEntriesCache cache) {
+      PathOwnersEntriesCache cache,
+      PluginSettings pluginSettings) {
     this.api = api;
     this.patchListCache = patchListCache;
     this.projectCache = projectCache;
@@ -115,6 +117,7 @@
     this.notesFactory = notesFactory;
     this.cfg = cfg;
     this.cache = cache;
+    this.pluginSettings = pluginSettings;
   }
 
   @Override
@@ -241,7 +244,8 @@
                 patchList,
                 cfg.expandGroups(),
                 projectNameKey.get(),
-                cache);
+                cache,
+                pluginSettings.globalLabel());
         Set<Account.Id> allReviewers = Sets.newHashSet();
         allReviewers.addAll(owners.get().values());
         allReviewers.addAll(owners.getReviewers().values());
diff --git a/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
index b651572..846f583 100644
--- a/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
+++ b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
@@ -47,7 +47,8 @@
       Provider<CurrentUser> currentUserProvider,
       ChangeNotes.Factory notesFactory,
       AutoAssignConfig cfg,
-      PathOwnersEntriesCache cache) {
+      PathOwnersEntriesCache cache,
+      PluginSettings pluginSettings) {
     super(
         api,
         patchListCache,
@@ -59,7 +60,8 @@
         currentUserProvider,
         notesFactory,
         cfg,
-        cache);
+        cache,
+        pluginSettings);
   }
 
   @Override
diff --git a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
index e7e437a..1e5023b 100644
--- a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
+++ b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PathOwners.java
@@ -103,7 +103,8 @@
       Map<String, FileDiffOutput> fileDiffMap,
       boolean expandGroups,
       String project,
-      PathOwnersEntriesCache cache)
+      PathOwnersEntriesCache cache,
+      Optional<LabelDefinition> globalLabel)
       throws InvalidOwnersFileException {
     this(
         accounts,
@@ -114,7 +115,8 @@
         getModifiedPaths(fileDiffMap),
         expandGroups,
         project,
-        cache);
+        cache,
+        globalLabel);
   }
 
   public PathOwners(
@@ -126,7 +128,8 @@
       DiffSummary diffSummary,
       boolean expandGroups,
       String project,
-      PathOwnersEntriesCache cache)
+      PathOwnersEntriesCache cache,
+      Optional<LabelDefinition> globalLabel)
       throws InvalidOwnersFileException {
     this(
         accounts,
@@ -137,7 +140,8 @@
         ImmutableSet.copyOf(diffSummary.getPaths()),
         expandGroups,
         project,
-        cache);
+        cache,
+        globalLabel);
   }
 
   public PathOwners(
@@ -149,7 +153,8 @@
       Set<String> modifiedPaths,
       boolean expandGroups,
       String project,
-      PathOwnersEntriesCache cache)
+      PathOwnersEntriesCache cache,
+      Optional<LabelDefinition> globalLabel)
       throws InvalidOwnersFileException {
     this.repositoryManager = repositoryManager;
     this.repository = repository;
@@ -170,7 +175,7 @@
     matchers = map.getMatchers();
     fileOwners = map.getFileOwners();
     fileGroupOwners = map.getFileGroupOwners();
-    label = map.getLabel();
+    label = globalLabel.or(map::getLabel);
   }
   /**
    * Returns a read only view of the paths to owners mapping.
diff --git a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PluginSettings.java b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PluginSettings.java
index 26b0f62..1fd7403 100644
--- a/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PluginSettings.java
+++ b/owners-common/src/main/java/com/googlesource/gerrit/owners/common/PluginSettings.java
@@ -15,6 +15,7 @@
 
 package com.googlesource.gerrit.owners.common;
 
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -23,6 +24,8 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import java.util.function.Supplier;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 
@@ -36,6 +39,8 @@
   private final boolean expandGroups;
   private final boolean enableSubmitRequirement;
 
+  private final Supplier<Optional<LabelDefinition>> globalLabel;
+
   @Inject
   public PluginSettings(PluginConfigFactory configFactory, @PluginName String ownersPluginName) {
     this.configFactory = configFactory;
@@ -48,6 +53,10 @@
     this.expandGroups = globalPluginConfig.getBoolean("owners", "expandGroups", true);
     this.enableSubmitRequirement =
         globalPluginConfig.getBoolean("owners", "enableSubmitRequirement", false);
+
+    this.globalLabel =
+        Suppliers.memoize(
+            () -> LabelDefinition.parse(globalPluginConfig.getString("owners", null, "label")));
   }
 
   /**
@@ -95,6 +104,19 @@
     return configFactory.getFromProjectConfigWithInheritance(projectKey, ownersPluginName);
   }
 
+  /**
+   * Global definition of the review label to use for the owners' plugin.
+   *
+   * <p>When the global label is set in the plugin.owners.label settings the value overrides any
+   * label name defined in the OWNERS files of any repository.
+   *
+   * @return the global label definition or {@link Optional#empty()} if there isn't any global label
+   *     definition.
+   */
+  public Optional<LabelDefinition> globalLabel() {
+    return globalLabel.get();
+  }
+
   // Logic copied from JGit's TestRepository
   private static String normalizeRef(String ref) {
     if (Constants.HEAD.equals(ref)) {
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
index 542ff6d..7ab294f 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PathOwnersTest.java
@@ -51,7 +51,11 @@
   private static final boolean EXPAND_GROUPS = true;
   private static final boolean DO_NOT_EXPAND_GROUPS = false;
   private static final String EXPECTED_LABEL = "expected-label";
+  private static final Short EXPECTED_LABEL_SCORE = 1;
+  private static final LabelDefinition EXPECTED_LABEL_DEFINITION =
+      new LabelDefinition(EXPECTED_LABEL, EXPECTED_LABEL_SCORE);
   private static final String A_LABEL = "a-label";
+  private static final String B_LABEL = "b-label";
   private static PathOwnersEntriesCache CACHE_MOCK = new PathOwnersEntriesCacheMock();
 
   public static final String CLASSIC_FILE_TXT = "classic/file.txt";
@@ -80,12 +84,33 @@
             Set.of(CLASSIC_FILE_TXT),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
     Set<Account.Id> ownersSet = owners.get().get(CLASSIC_OWNERS);
     assertEquals(2, ownersSet.size());
     assertTrue(ownersSet.contains(USER_A_ID));
     assertTrue(ownersSet.contains(USER_B_ID));
     assertTrue(owners.expandGroups());
+    assertThat(owners.getLabel()).isEmpty();
+  }
+
+  @Test
+  public void testGlobalLabel() throws Exception {
+    mockOwners(USER_A_EMAIL_COM, USER_B_EMAIL_COM);
+
+    PathOwners owners =
+        new PathOwners(
+            accounts,
+            repositoryManager,
+            repository,
+            emptyList(),
+            branch,
+            Set.of(CLASSIC_FILE_TXT),
+            EXPAND_GROUPS,
+            "foo",
+            CACHE_MOCK,
+            Optional.of(EXPECTED_LABEL_DEFINITION));
+    assertThat(owners.getLabel()).hasValue(EXPECTED_LABEL_DEFINITION);
   }
 
   @Test
@@ -102,7 +127,8 @@
             Set.of(CLASSIC_FILE_TXT),
             DO_NOT_EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
     Set<String> ownersSet = owners.getFileGroupOwners().get(CLASSIC_FILE_TXT);
     assertEquals(2, ownersSet.size());
     assertTrue(ownersSet.contains(USER_A));
@@ -124,7 +150,8 @@
             Set.of(CLASSIC_FILE_TXT),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
     Set<Account.Id> ownersSet = owners.get().get(CLASSIC_OWNERS);
     assertEquals(0, ownersSet.size());
   }
@@ -149,7 +176,8 @@
             Set.of("classic/file.txt"),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
     Set<Account.Id> ownersSet2 = owners2.get().get(CLASSIC_OWNERS);
 
     // in this case we are inheriting the acct3 from /OWNERS
@@ -164,6 +192,50 @@
   }
 
   @Test
+  public void testClassicWithInheritanceAndGlobalLabel() throws Exception {
+    expectConfig("OWNERS", createConfig(true, Optional.of(A_LABEL), owners()));
+    expectConfig(CLASSIC_OWNERS, createConfig(true, Optional.of(B_LABEL), owners()));
+
+    replayAll();
+
+    PathOwners owners2 =
+        new PathOwners(
+            accounts,
+            repositoryManager,
+            repository,
+            emptyList(),
+            branch,
+            Set.of("classic/file.txt"),
+            EXPAND_GROUPS,
+            "foo",
+            CACHE_MOCK,
+            Optional.of(EXPECTED_LABEL_DEFINITION));
+    assertThat(owners2.getLabel()).hasValue(EXPECTED_LABEL_DEFINITION);
+  }
+
+  @Test
+  public void testClassicWithoutInheritanceAndGlobalLabel() throws Exception {
+    expectConfig("OWNERS", createConfig(false, Optional.of(A_LABEL), owners()));
+    expectConfig(CLASSIC_OWNERS, createConfig(false, Optional.of(B_LABEL), owners()));
+
+    replayAll();
+
+    PathOwners owners2 =
+        new PathOwners(
+            accounts,
+            repositoryManager,
+            repository,
+            emptyList(),
+            branch,
+            Set.of("classic/file.txt"),
+            EXPAND_GROUPS,
+            "foo",
+            CACHE_MOCK,
+            Optional.of(EXPECTED_LABEL_DEFINITION));
+    assertThat(owners2.getLabel()).hasValue(EXPECTED_LABEL_DEFINITION);
+  }
+
+  @Test
   public void testRootInheritFromProject() throws Exception {
     expectConfig("OWNERS", "master", createConfig(true, owners()));
     expectConfig(
@@ -188,7 +260,8 @@
             Set.of(fileName),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
 
     Map<String, Set<Account.Id>> fileOwners = owners.getFileOwners();
     assertEquals(1, fileOwners.size());
@@ -233,7 +306,8 @@
             Set.of(fileName),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
 
     Map<String, Set<Account.Id>> fileOwners = owners.getFileOwners();
     assertEquals(fileOwners.size(), 1);
@@ -282,7 +356,8 @@
             Set.of(sqlFileName, javaFileName),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
 
     Map<String, Set<Account.Id>> fileOwners = owners.getFileOwners();
     assertEquals(fileOwners.size(), 2);
@@ -326,7 +401,8 @@
             Set.of("dir/subdir/file.txt"),
             EXPAND_GROUPS,
             "foo",
-            CACHE_MOCK);
+            CACHE_MOCK,
+            Optional.empty());
     Set<Account.Id> ownersSet = owners.get().get("dir/subdir/OWNERS");
 
     assertEquals(3, ownersSet.size());
@@ -406,7 +482,8 @@
             Set.of("dir/subdir/file.txt"),
             EXPAND_GROUPS,
             "foo",
-            cacheMock);
+            cacheMock,
+            Optional.empty());
 
     assertThat(owners.getFileOwners()).isNotEmpty();
     int expectedCacheCalls =
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PluginSettingsTest.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PluginSettingsTest.java
index c4c75d4..2aecd88 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PluginSettingsTest.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/PluginSettingsTest.java
@@ -18,6 +18,7 @@
 import static org.mockito.Mockito.when;
 
 import com.google.gerrit.server.config.PluginConfigFactory;
+import java.util.Optional;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.junit.Test;
@@ -81,4 +82,21 @@
     assertThat(pluginSettings.isBranchDisabled(branchName1)).isTrue();
     assertThat(pluginSettings.isBranchDisabled(branchName2)).isTrue();
   }
+
+  @Test
+  public void globalLabelIsEmptyByDefault() {
+    setupMocks(new Config());
+
+    assertThat(pluginSettings.globalLabel()).isEqualTo(Optional.empty());
+  }
+
+  @Test
+  public void globalLabelSetByConfig() {
+    LabelDefinition globalLabelName = new LabelDefinition("Custom-Label", (short) 2);
+    Config pluginConfig = new Config();
+    pluginConfig.setString("owners", null, "label", "Custom-Label,2");
+    setupMocks(pluginConfig);
+
+    assertThat(pluginSettings.globalLabel()).isEqualTo(Optional.of(globalLabelName));
+  }
 }
diff --git a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
index a035d93..821db6a 100644
--- a/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
+++ b/owners-common/src/test/java/com/googlesource/gerrit/owners/common/RegexTest.java
@@ -31,6 +31,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Before;
@@ -156,7 +157,8 @@
                 "project/file.sql"), // only .sql matching b,c
             EXPAND_GROUPS,
             "foo",
-            new PathOwnersEntriesCacheMock());
+            new PathOwnersEntriesCacheMock(),
+            Optional.empty());
 
     // assertions on classic owners
     Set<Account.Id> ownersSet = owners.get().get("project/OWNERS");
@@ -263,7 +265,8 @@
             Set.of("project/file.sql", "another.txt"),
             EXPAND_GROUPS,
             "foo",
-            new PathOwnersEntriesCacheMock());
+            new PathOwnersEntriesCacheMock(),
+            Optional.empty());
 
     Set<String> ownedFiles = owners.getFileOwners().keySet();
     assertThat(ownedFiles).containsExactly("project/file.sql");
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
index 7c532c6..84cc6a2 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java
@@ -73,7 +73,8 @@
                   patchList,
                   settings.expandGroups(),
                   projectState.getName(),
-                  cache);
+                  cache,
+                  settings.globalLabel());
             } catch (InvalidOwnersFileException e) {
               // re-throw exception as it is already logged but more importantly it is nicely
               // handled by the prolog rules evaluator and results in prolog rule error
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
index 0d8dac4..64638bc 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
@@ -216,7 +216,8 @@
                 getDiff(nameKey, cd.currentPatchSet().commitId()),
                 pluginSettings.expandGroups(),
                 nameKey.get(),
-                cache);
+                cache,
+                pluginSettings.globalLabel());
 
         return pathOwners;
       }
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
index a724239..bbe3007 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
@@ -114,7 +114,8 @@
               changePaths,
               pluginSettings.expandGroups(),
               project.get(),
-              cache);
+              cache,
+              pluginSettings.globalLabel());
 
       Map<String, Set<GroupOwner>> fileExpandedOwners =
           Maps.transformValues(
diff --git a/owners/src/main/resources/Documentation/config.md b/owners/src/main/resources/Documentation/config.md
index debba42..89c6248 100644
--- a/owners/src/main/resources/Documentation/config.md
+++ b/owners/src/main/resources/Documentation/config.md
@@ -28,6 +28,18 @@
       expandGroups = false
     ```
 
+owners.label
+:   Global override for the label and score, separated by a comma, to use by
+    the owners of changes for approving them. When defined, it overrides any
+    other label definition set by the OWNERS at any level in any project.
+
+    Example:
+
+    ```
+    [owners]
+      label = Code-Review, 1
+    ```
+
 <a name="owners.enableSubmitRequirement">owners.enableSubmitRequirement</a>
 :   If set to `true` the approvals are evaluated through the owners plugin
     provided submit rule without a need of prolog predicate being added to a