Merge "PathCodeOwners: Skip resolving global imports that will be ignored"
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index 384de36..11b9b6b 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.plugins.codeowners.backend;
 
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toSet;
@@ -236,6 +235,7 @@
           codeOwnerConfig.ignoreParentCodeOwners());
       getGlobalCodeOwnerSets(codeOwnerConfig)
           .forEach(resolvedCodeOwnerConfigBuilder::addCodeOwnerSet);
+      boolean globalCodeOwnersIgnored = false;
       for (CodeOwnerSet codeOwnerSet :
           getMatchingPerFileCodeOwnerSets(codeOwnerConfig).collect(toImmutableSet())) {
         messages.add(
@@ -243,13 +243,35 @@
                 "per-file code owner set with path expressions %s matches",
                 codeOwnerSet.pathExpressions()));
         resolvedCodeOwnerConfigBuilder.addCodeOwnerSet(codeOwnerSet);
+        if (codeOwnerSet.ignoreGlobalAndParentCodeOwners()) {
+          globalCodeOwnersIgnored = true;
+        }
       }
 
-      // Resolve all global imports.
+      // Resolve global imports.
+      ImmutableList.Builder<UnresolvedImport> unresolvedImports = ImmutableList.builder();
       ImmutableSet<CodeOwnerConfigImport> globalImports = getGlobalImports(0, codeOwnerConfig);
-      OptionalResultWithMessages<List<UnresolvedImport>> unresolvedGlobalImports =
-          resolveImports(codeOwnerConfig.key(), globalImports, resolvedCodeOwnerConfigBuilder);
+      OptionalResultWithMessages<List<UnresolvedImport>> unresolvedGlobalImports;
+      if (!globalCodeOwnersIgnored) {
+        unresolvedGlobalImports =
+            resolveImports(codeOwnerConfig.key(), globalImports, resolvedCodeOwnerConfigBuilder);
+      } else {
+        // skip global import with mode GLOBAL_CODE_OWNER_SETS_ONLY,
+        // since we already know that global code owners will be ignored, we do not need to resolve
+        // these imports
+        unresolvedGlobalImports =
+            resolveImports(
+                codeOwnerConfig.key(),
+                globalImports.stream()
+                    .filter(
+                        codeOwnerConfigImport ->
+                            codeOwnerConfigImport.referenceToImportedCodeOwnerConfig().importMode()
+                                != CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY)
+                    .collect(toImmutableSet()),
+                resolvedCodeOwnerConfigBuilder);
+      }
       messages.addAll(unresolvedGlobalImports.messages());
+      unresolvedImports.addAll(unresolvedGlobalImports.get());
 
       // Remove all global code owner sets if any per-file code owner set has the
       // ignoreGlobalAndParentCodeOwners flag set to true (as in this case they are ignored and
@@ -298,16 +320,12 @@
       OptionalResultWithMessages<List<UnresolvedImport>> unresolvedPerFileImports =
           resolveImports(codeOwnerConfig.key(), perFileImports, resolvedCodeOwnerConfigBuilder);
       messages.addAll(unresolvedPerFileImports.messages());
+      unresolvedImports.addAll(unresolvedPerFileImports.get());
 
       this.pathCodeOwnersResult =
           OptionalResultWithMessages.create(
               PathCodeOwnersResult.create(
-                  path,
-                  resolvedCodeOwnerConfigBuilder.build(),
-                  Stream.concat(
-                          unresolvedGlobalImports.get().stream(),
-                          unresolvedPerFileImports.get().stream())
-                      .collect(toImmutableList())),
+                  path, resolvedCodeOwnerConfigBuilder.build(), unresolvedImports.build()),
               messages);
       logger.atFine().log("path code owners result = %s", pathCodeOwnersResult);
       return this.pathCodeOwnersResult;
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
index d4ec968..dce1d52 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
@@ -2006,6 +2006,66 @@
         .isFalse();
   }
 
+  @Test
+  public void
+      perFileRuleThatIsImportedByAGlobalImportIsRespectedIfALocalPerFileRuleIgnoresGlobalCodeOwners()
+          throws Exception {
+    TestAccount user2 = accountCreator.user2();
+    TestAccount user3 =
+        accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
+
+    // create importing config that has a global import with mode ALL and a per-file rule for md
+    // files that ignores global and parent code owners
+    CodeOwnerConfig.Key importingCodeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(admin.email())
+            .addImport(
+                CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+            .addCodeOwnerSet(
+                CodeOwnerSet.builder()
+                    .addPathExpression(testPathExpressions.matchFileType("md"))
+                    .setIgnoreGlobalAndParentCodeOwners()
+                    .addCodeOwnerEmail(user.email())
+                    .build())
+            .create();
+
+    // create imported config that has a matching per-file rule
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/bar/")
+        .addCodeOwnerEmail(user2.email())
+        .addCodeOwnerSet(
+            CodeOwnerSet.builder()
+                .addPathExpression(testPathExpressions.matchFileType("md"))
+                .addCodeOwnerEmail(user3.email())
+                .build())
+        .create();
+
+    Optional<PathCodeOwners> pathCodeOwners =
+        pathCodeOwnersFactory.create(
+            transientCodeOwnerConfigCacheProvider.get(),
+            importingCodeOwnerConfigKey,
+            projectOperations.project(project).getHead("master"),
+            Paths.get("/foo.md"));
+    assertThat(pathCodeOwners).isPresent();
+
+    // Expectation: we get the code owner from the matching per-file rule in the importing code
+    // owner config and the code owner from the matching per-file rule in the imported code owner
+    // config, the global code owners are ignored since there is a matching per-file rule that
+    // ignores parent and global code owners
+    assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+        .comparingElementsUsing(hasEmail())
+        .containsExactly(user.email(), user3.email());
+    assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
+        .isFalse();
+  }
+
   private CodeOwnerConfig.Builder createCodeOwnerBuilder() {
     return CodeOwnerConfig.builder(
         CodeOwnerConfig.Key.create(BranchNameKey.create(project, "master"), Paths.get("/")),