Fix wrongly returned import when per file owner is transitively imported

If an OWNERS file A has a global import of an OWNERS file B that has a
per-file import, then the per-file import was returned twice, once as
expected for OWNERS file B and once wrongly for OWNERS file A.

When resolving imports for per-file code owner sets we must only look at
the per-file code owner sets from the OWNERS file that is being
resolving and not at per-file code owner sets which have already been
imported via global imports.

Change-Id: I0ec04d121f4b1f81ca75e8f52c5e15c3a75f32b1
Signed-off-by: Edwin Kempin <ekempin@google.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/plugins/code-owners/+/434757
Tested-by: Zuul <zuul-63@gerritcodereview-ci.iam.gserviceaccount.com>
Reviewed-by: Kamil Musin <kamilm@google.com>
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index 40eaa21..05af4e3 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -248,8 +248,9 @@
       getGlobalCodeOwnerSets(codeOwnerConfig)
           .forEach(resolvedCodeOwnerConfigBuilder::addCodeOwnerSet);
       boolean globalCodeOwnersIgnored = false;
-      for (CodeOwnerSet codeOwnerSet :
-          getMatchingPerFileCodeOwnerSets(codeOwnerConfig).collect(toImmutableSet())) {
+      ImmutableSet<CodeOwnerSet> matchingPerFileCodeOwnerSets =
+          getMatchingPerFileCodeOwnerSets(codeOwnerConfig).collect(toImmutableSet());
+      for (CodeOwnerSet codeOwnerSet : matchingPerFileCodeOwnerSets) {
         messages.add(
             String.format(
                 "per-file code owner set with path expressions %s matches",
@@ -329,7 +330,7 @@
 
       // Resolve per-file imports.
       ImmutableSet<CodeOwnerImport> perFileImports =
-          getPerFileImports(0, codeOwnerConfig, resolvedCodeOwnerConfigBuilder.codeOwnerSets());
+          getPerFileImports(0, codeOwnerConfig, matchingPerFileCodeOwnerSets);
       OptionalResultWithMessages<CodeOwnerConfigImports> perFileImportedCodeOwnerConfigs =
           resolveImports(codeOwnerConfig.key(), perFileImports, resolvedCodeOwnerConfigBuilder);
       messages.addAll(perFileImportedCodeOwnerConfigs.messages());
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
index 064d8f6..7f9492f 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
@@ -2836,6 +2836,83 @@
     assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
   }
 
+  @Test
+  public void transitiveImportsOfPerFileCodeOwners() throws Exception {
+    TestAccount mdCodeOwner =
+        accountCreator.create(
+            "mdCodeOwner", "mdCodeOwner@example.com", "Md Code Owner", /* displayName= */ null);
+
+    CodeOwnerConfigReference barCodeOwnerConfigReference =
+        CodeOwnerConfigReference.create(
+            CodeOwnerConfigImportMode.ALL, "/bar/" + getCodeOwnerConfigFileName());
+    CodeOwnerConfig.Key fooCodeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .fileName("OWNERS")
+            .addImport(barCodeOwnerConfigReference)
+            .create();
+
+    CodeOwnerConfigReference bazCodeOwnerConfigReference =
+        CodeOwnerConfigReference.create(
+            CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+            "/baz/" + getCodeOwnerConfigFileName());
+    CodeOwnerConfig.Key barCodeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .fileName("OWNERS")
+            .folderPath("/bar/")
+            .addCodeOwnerSet(
+                CodeOwnerSet.builder()
+                    .addPathExpression(testPathExpressions.matchFileType("md"))
+                    .addImport(bazCodeOwnerConfigReference)
+                    .build())
+            .create();
+
+    CodeOwnerConfig.Key bazCodeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/baz/")
+            .fileName("OWNERS")
+            .addCodeOwnerEmail(mdCodeOwner.email())
+            .create();
+
+    CodeOwnerConfig fooCodeOwnerConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(fooCodeOwnerConfigKey).get();
+    CodeOwnerConfig barCodeOwnerConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(barCodeOwnerConfigKey).get();
+    CodeOwnerConfig bazCodeOwnerConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(bazCodeOwnerConfigKey).get();
+
+    Optional<PathCodeOwners> pathCodeOwners =
+        pathCodeOwnersFactory.create(
+            transientCodeOwnerConfigCacheProvider.get(),
+            fooCodeOwnerConfigKey,
+            projectOperations.project(project).getHead("master"),
+            Path.of("/foo/bar/baz.md"));
+    assertThat(pathCodeOwners).isPresent();
+
+    // Expectation: we get the per-file code owner of the code owner config that is transitively
+    // imported.
+    PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+    assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+        .comparingElementsUsing(hasEmail())
+        .containsExactly(mdCodeOwner.email());
+    assertThat(pathCodeOwnersResult.resolvedImports())
+        .containsExactly(
+            CodeOwnerConfigImport.createResolvedImport(
+                fooCodeOwnerConfig, barCodeOwnerConfig, barCodeOwnerConfigReference),
+            CodeOwnerConfigImport.createResolvedImport(
+                barCodeOwnerConfig, bazCodeOwnerConfig, bazCodeOwnerConfigReference));
+    assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
+  }
+
   private CodeOwnerConfig.Builder createCodeOwnerBuilder() {
     return CodeOwnerConfig.builder(
         CodeOwnerConfig.Key.create(BranchNameKey.create(project, "master"), Path.of("/")),