Merge changes Iafb16808,I2feeb31f,I1ef01100,I51873396,I0a3d985a, ...

* changes:
  Prefer explicitly mentioned code owners
  Test getting code owners when all users are global code owners
  Fix getAllUsersAsCodeOwners_allVisible test
  JgitPathTest: Remove unneeded import
  JGitPathTest: Test toString/equals/hashCode methods
  Test adding config with ALL import of config that is added in same commit
  Include revision into error message when rejecting invalid import
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
index 71cb59f..3dd3ab9 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
@@ -370,13 +370,17 @@
     logger.atFine().log("filling up with random users");
     codeOwners.addAll(
         filterCodeOwners(
-            rsrc,
-            // ask for 2 times the number of users that we need so that we still have enough
-            // suggestions when some users are removed by the filterCodeOwners call or if the
-            // returned users were already present in codeOwners
-            getRandomVisibleUsers(2 * limit - codeOwners.size())
-                .map(CodeOwner::create)
-                .collect(toImmutableSet())));
+                rsrc,
+                // ask for 2 times the number of users that we need so that we still have enough
+                // suggestions when some users are removed by the filterCodeOwners call or if the
+                // returned users were already present in codeOwners
+                getRandomVisibleUsers(2 * limit - codeOwners.size())
+                    .map(CodeOwner::create)
+                    .collect(toImmutableSet()))
+            .stream()
+            .filter(codeOwner -> !codeOwners.contains(codeOwner))
+            .limit(limit - codeOwners.size())
+            .collect(toImmutableSet()));
   }
 
   /**
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
index 17235cd..9458289 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
@@ -872,10 +872,11 @@
             importType,
             codeOwnerConfigFilePath,
             String.format(
-                "'%s' does not exist (project = %s, branch = %s)",
+                "'%s' does not exist (project = %s, branch = %s, revision = %s)",
                 codeOwnerConfigReference.filePath(),
                 keyOfImportedCodeOwnerConfig.branchNameKey().project().get(),
-                keyOfImportedCodeOwnerConfig.branchNameKey().shortName()));
+                keyOfImportedCodeOwnerConfig.branchNameKey().shortName(),
+                revision.get().name()));
       }
     } catch (StorageException storageException) {
       if (getInvalidConfigCause(storageException).isPresent()) {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
index 4dbac76..6e14d21 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
@@ -642,6 +642,30 @@
   }
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  public void getAllUsersAsGlobalCodeOwners() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+
+    List<CodeOwnerInfo> codeOwnerInfos = queryCodeOwners("/foo/bar/baz.md");
+    assertThat(codeOwnerInfos)
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(user.id(), user2.id(), admin.id());
+
+    // Query code owners with a limit.
+    requestScopeOperations.setApiUser(user.id());
+    codeOwnerInfos = queryCodeOwners(getCodeOwnersApi().query().withLimit(2), "/foo/bar/baz.md");
+    assertThat(codeOwnerInfos).hasSize(2);
+    assertThatList(codeOwnerInfos)
+        .element(0)
+        .hasAccountIdThat()
+        .isAnyOf(user.id(), user2.id(), admin.id());
+    assertThatList(codeOwnerInfos)
+        .element(1)
+        .hasAccountIdThat()
+        .isAnyOf(user.id(), user2.id(), admin.id());
+  }
+
+  @Test
   @GerritConfig(
       name = "plugin.code-owners.globalCodeOwner",
       values = {"global.owner1@example.com", "global.owner2@example.com"})
@@ -861,6 +885,7 @@
         .containsExactly(user.id(), user2.id(), admin.id());
 
     // Query code owners with a limit.
+    requestScopeOperations.setApiUser(user.id());
     codeOwnerInfos = queryCodeOwners(getCodeOwnersApi().query().withLimit(2), "/foo/bar/baz.md");
     assertThat(codeOwnerInfos).hasSize(2);
     assertThatList(codeOwnerInfos)
@@ -1016,6 +1041,33 @@
   }
 
   @Test
+  public void getAllUsersAsCodeOwners_explicitlyMentionedCodeOwnersArePreferred() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+
+    // Add a code owner config that assigns code ownership to user2 and all users.
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(user2.email())
+        .addCodeOwnerEmail("*")
+        .create();
+
+    // Query code owners with limits, "*" is resolved to random users, but user2 should always be
+    // included since this user is set explicitly as code owner
+    List<CodeOwnerInfo> codeOwnerInfos =
+        queryCodeOwners(getCodeOwnersApi().query().withLimit(2), "/foo/bar/baz.md");
+    assertThat(codeOwnerInfos).hasSize(2);
+    assertThatList(codeOwnerInfos).comparingElementsUsing(hasAccountId()).contains(user2.id());
+
+    codeOwnerInfos = queryCodeOwners(getCodeOwnersApi().query().withLimit(1), "/foo/bar/baz.md");
+    assertThatList(codeOwnerInfos)
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(user2.id());
+  }
+
+  @Test
   public void getCodeOwnersProvidingASeedMakesSortOrderStableAcrocssRequests() throws Exception {
     TestAccount user2 = accountCreator.user2();
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
index fe9bdc1..314747c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -289,8 +289,9 @@
   }
 
   @Test
-  public void canUploadConfigWithoutIssues_withImportOfConfigThatIsAddedInSameCommit()
-      throws Exception {
+  public void
+      canUploadConfigWithImportOfConfigThatIsAddedInSameCommit_importModeGlobalCodeOwnersOnly()
+          throws Exception {
     // imports are not supported for the proto backend
     assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
 
@@ -302,7 +303,6 @@
             CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
             codeOwnerConfigOperations.codeOwnerConfig(keyOfImportedCodeOwnerConfig).getFilePath());
 
-    // Create a code owner config with import and without issues.
     PushOneCommit.Result r =
         createChange(
             "Add code owners",
@@ -330,6 +330,42 @@
   }
 
   @Test
+  public void canUploadConfigWithImportOfConfigThatIsAddedInSameCommit_importModeAll()
+      throws Exception {
+    // imports are not supported for the proto backend
+    assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+    CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
+    CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig = createCodeOwnerConfigKey("/foo/");
+
+    CodeOwnerConfigReference codeOwnerConfigReference =
+        CodeOwnerConfigReference.create(
+            CodeOwnerConfigImportMode.ALL,
+            codeOwnerConfigOperations.codeOwnerConfig(keyOfImportedCodeOwnerConfig).getFilePath());
+
+    PushOneCommit.Result r =
+        createChange(
+            "Add code owners",
+            ImmutableMap.of(
+                codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getJGitFilePath(),
+                format(
+                    CodeOwnerConfig.builder(codeOwnerConfigKey, TEST_REVISION)
+                        .addImport(codeOwnerConfigReference)
+                        .addCodeOwnerSet(
+                            CodeOwnerSet.builder().addCodeOwnerEmail(admin.email()).build())
+                        .build()),
+                codeOwnerConfigOperations
+                    .codeOwnerConfig(keyOfImportedCodeOwnerConfig)
+                    .getJGitFilePath(),
+                format(
+                    CodeOwnerConfig.builder(keyOfImportedCodeOwnerConfig, TEST_REVISION)
+                        .addCodeOwnerSet(
+                            CodeOwnerSet.builder().addCodeOwnerEmail(user.email()).build())
+                        .build())));
+    assertOkWithHints(r, "code owner config files validated, no issues found");
+  }
+
+  @Test
   @GerritConfig(name = "plugin.code-owners.backend", value = "non-existing-backend")
   public void canUploadNonParseableConfigIfCodeOwnersPluginConfigurationIsInvalid()
       throws Exception {
@@ -1355,13 +1391,15 @@
         r,
         "invalid code owner config files",
         String.format(
-            "invalid %s import in '%s': '%s' does not exist (project = %s, branch = master)",
+            "invalid %s import in '%s': '%s' does not exist (project = %s, branch = master,"
+                + " revision = %s)",
             importType.getType(),
             codeOwnerConfigOperations.codeOwnerConfig(keyOfImportingCodeOwnerConfig).getFilePath(),
             codeOwnerConfigOperations
                 .codeOwnerConfig(keyOfNonExistingCodeOwnerConfig)
                 .getFilePath(),
-            project.get()));
+            project.get(),
+            r.getCommit().name()));
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/plugins/codeowners/util/JgitPathTest.java b/javatests/com/google/gerrit/plugins/codeowners/util/JgitPathTest.java
index 25f4035..4b73459 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/util/JgitPathTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/util/JgitPathTest.java
@@ -19,12 +19,11 @@
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
 import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
-import com.google.gerrit.plugins.codeowners.util.JgitPath;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Test;
 
-/** Tests for {@link JgitPath}. */
+/** Tests for {@link com.google.gerrit.plugins.codeowners.util.JgitPath}. */
 public class JgitPathTest extends AbstractCodeOwnersTest {
   @Test
   public void getJgitPathOfStringPath() throws Exception {
@@ -57,4 +56,29 @@
     assertThat(JgitPath.of("foo/bar/OWNERS").getAsAbsolutePath())
         .isEqualTo(Paths.get("/foo/bar/OWNERS"));
   }
+
+  @Test
+  public void testToString() throws Exception {
+    String path = "foo/bar/baz.md";
+    assertThat(JgitPath.of(path).toString()).isEqualTo(path);
+    assertThat(JgitPath.of("/" + path).toString()).isEqualTo(path);
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    String path = "foo/bar/baz.md";
+    assertThat(JgitPath.of(path)).isEqualTo(JgitPath.of(path));
+    assertThat(JgitPath.of("/" + path)).isEqualTo(JgitPath.of(path));
+    assertThat(JgitPath.of("/" + path)).isNotEqualTo(JgitPath.of("foo/bar/baz.txt"));
+    assertThat(JgitPath.of("/" + path)).isNotEqualTo(new Object());
+  }
+
+  @Test
+  public void testHashCode() throws Exception {
+    String path = "foo/bar/baz.md";
+    assertThat(JgitPath.of(path).hashCode()).isEqualTo(JgitPath.of(path).hashCode());
+    assertThat(JgitPath.of("/" + path).hashCode()).isEqualTo(JgitPath.of(path).hashCode());
+    assertThat(JgitPath.of("/" + path).hashCode())
+        .isNotEqualTo(JgitPath.of("foo/bar/baz.txt").hashCode());
+  }
 }