Return OWNERS files that were read for computing code owners of a file
With this change we extend the response of the List Code Owners REST
endpoints to return the OWNERS files which have been taken into account
for computing the code owners of the file.
Inspecting this list of OWNERS files is useful when the suggested code
owners do not match the expectation of the user as it allows the user to
see from which OWNERS files the code owners were loaded. E.g. if an
expected code owner is missing they can easily determine to which OWNERS
file this user should be added as a code owner.
Another case where this can help to avoid user confusion is when a
project contains different sets of OWNERS files (e.g. 'OWNERS' files and
'OWNERS.android' files) as it shows to the user which set of OWNERS
files is being used (depends on the code owners project configuration,
see codeOwners.fileExtension setting).
When computing the code owners for a file, the List Code Owners REST
endpoints iterate over the relevant OWNERS files, starting at the
directory of the file for which code owners are requested, e.g. if code
owners are requested for file "/foo/bar/baz/file.txt" the inspected
OWNERS files, if they exist, are '/foo/bar/baz/OWNERS',
'/foo/bar/OWNERS', '/foo/OWNERS', '/OWNERS' and finally '/OWNERS' in
refs/meta/config (this file defines the default code owners). These
files are collected as we iterate over them and are returned in the
order in which they are evaluated.
In addition, OWNERS files may import other OWNERS files. Imported OWNERS
files are returned as part of the JSON that represents the importing
OWNERS file. The imported OWNERS files can be from another project or
branch and can import further OWNERS files themselves. When importing
OWNERS files there are 2 import modes: ALL -> import all code owners
(global code owners + per-file code owners), GLOBAL_CODE_OWNER_SETS_ONLY
-> import only global code owners (but no per-file code owners). Which
import mode is being used depends on the keyword that is used to import
an OWNERS file ('include' keyword = ALL, 'file' keyword =
GLOBAL_CODE_OWNER_SETS_ONLY). To be able to differentiate between the 2
import modes the JSON for the imported OWNERS file contains a field for
the import mode.
It's possible that OWNERS files import other OWNERS files which cannot
be resolved (e.g. the imported OWNERS file no longer exists). In this
case the import is ignored. To make this transparent to users, we also
return unresolved imports as part of the JSON that is returned for the
importing OWNERS file. The JSON that represents the unresolved import
contains an error message in this case that explains why this import
couldn't be resolved.
Resolving imports is implemented in PathCodeOwners. PathCodeOwners
already returned unresolved imports and we extended PathCodeOwners to
return resolved imports as well (see new field in PathCodeOwnersResult).
Unresolved and resolved imports are represented by
ImportedCodeOwnerConfig (previously named UnresolvedImport) which
consists of the key of the importing OWNERS file, the key of the
imported OWNERS file, the code owner config reference that contains
information about the import mode and an optional error message that is
set if the imported code owner config couldn't be resolved.
CodeOwnerResolver is a wrapper around PathCodeOwners and has been
extended to provide the resolved and unresolved imports that were found
by PathCodeOwners to its caller, the List Code Owners REST endpoints, so
that they can include this information into the JSON that is returned to
the client.
The OWNERS files that were taken into account for computing the code
owners are returned as CodeOwnerConfigFileInfo entities that are
constructed by CodeOwnerConfigFileJson.
Bug: Google b/271940778
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I951772554489095af074777792697c0e539ef453
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
index 0e872a9..9830533 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
@@ -38,6 +38,9 @@
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestCodeOwnerConfigCreation.Builder;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference;
import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.StatusConfig;
@@ -341,4 +344,13 @@
}
throw new IllegalStateException("unknown code owner backend: " + backend.getClass().getName());
}
+
+ protected CodeOwnerConfigReference createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode importMode, CodeOwnerConfig.Key codeOwnerConfigKey) {
+ return CodeOwnerConfigReference.builder(
+ importMode, codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath())
+ .setProject(codeOwnerConfigKey.project())
+ .setBranch(codeOwnerConfigKey.branchNameKey().branch())
+ .build();
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerConfigFileInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerConfigFileInfo.java
new file mode 100644
index 0000000..41fa62b
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerConfigFileInfo.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.api;
+
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
+import java.util.List;
+
+/**
+ * Representation of a code owner config file in the REST API.
+ *
+ * <p>This class determines the JSON format of code owner config files in the REST API.
+ *
+ * <p>The JSON only contains the location of the code owner config file (project, branch, path) and
+ * information about the imported code owner config files. It's not a representation of the code
+ * owner config file content (the content of a code owner config file is represented by {@link
+ * CodeOwnerConfigInfo}).
+ */
+public class CodeOwnerConfigFileInfo {
+ /**
+ * The name of the project from which the code owner config was loaded, or for unresolved imports,
+ * from which the code owner config was supposed to be loaded.
+ */
+ public String project;
+
+ /**
+ * The name of the branch from which the code owner config was loaded, or for unresolved imports,
+ * from which the code owner config was supposed to be loaded.
+ */
+ public String branch;
+
+ /** The path of the code owner config file. */
+ public String path;
+
+ /** Imported code owner config files. */
+ public List<CodeOwnerConfigFileInfo> imports;
+
+ /** Imported code owner config files that couldn't be resolved. */
+ public List<CodeOwnerConfigFileInfo> unresolvedImports;
+
+ /**
+ * Message explaining why this code owner config couldn't be resolved.
+ *
+ * <p>Only set for unresolved imports.
+ */
+ public String unresolvedErrorMessage;
+
+ /**
+ * The import mode.
+ *
+ * <p>Only set for imports.
+ */
+ public CodeOwnerConfigImportMode importMode;
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
index d699029..763dff2 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
@@ -34,6 +34,9 @@
*/
public Boolean ownedByAllUsers;
+ /** The code owner config files that have been inspected to gather the code owners. */
+ public List<CodeOwnerConfigFileInfo> codeOwnerConfigs;
+
/**
* Debug logs that may help to understand why a user is or isn't suggested as a code owner.
*
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImport.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImport.java
new file mode 100644
index 0000000..a75c15b
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImport.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.backend;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import java.util.Optional;
+
+/**
+ * Information about an import of a {@link CodeOwnerConfig}.
+ *
+ * <p>Contains the keys of the importing and the imported code owner config, as well as the
+ * reference that the importing code owner config uses to reference the imported code owner config
+ * (contains the import mode).
+ *
+ * <p>It's possible that this class represents non-resolveable imports (e.g. an import of a
+ * non-existing code owner config). In this case an error message is contained that explains why the
+ * import couldn't be resolved.
+ */
+@AutoValue
+public abstract class CodeOwnerConfigImport {
+ /** Key of the importing code owner config. */
+ public abstract CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig();
+
+ /** Key of the imported code owner config. */
+ public abstract CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig();
+
+ /** The code owner config reference that references the imported code owner config. */
+ public abstract CodeOwnerConfigReference codeOwnerConfigReference();
+
+ /**
+ * If the import couldn't be resolved, a message explaining why the code owner config reference
+ * couldn't be resolved.
+ */
+ public abstract Optional<String> errorMessage();
+
+ @Override
+ public final String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("keyOfImportingCodeOwnerConfig", keyOfImportingCodeOwnerConfig())
+ .add("keyOfImportedCodeOwnerConfig", keyOfImportedCodeOwnerConfig())
+ .add("codeOwnerConfigReference", codeOwnerConfigReference())
+ .add("errorMessage", errorMessage())
+ .toString();
+ }
+
+ /** Creates a {@link CodeOwnerConfigImport} instance for an unresolved import. */
+ @VisibleForTesting
+ public static CodeOwnerConfigImport createUnresolvedImport(
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig,
+ CodeOwnerConfigReference codeOwnerConfigReference,
+ String errorMessage) {
+ return new AutoValue_CodeOwnerConfigImport(
+ keyOfImportingCodeOwnerConfig,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ Optional.of(errorMessage));
+ }
+
+ /** Creates a {@link CodeOwnerConfigImport} instance for a resolved import. */
+ @VisibleForTesting
+ public static CodeOwnerConfigImport createResolvedImport(
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig,
+ CodeOwnerConfigReference codeOwnerConfigReference) {
+ return new AutoValue_CodeOwnerConfigImport(
+ keyOfImportingCodeOwnerConfig,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ Optional.empty());
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
index 07894ce..7db6b68 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
@@ -50,7 +50,6 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -234,6 +233,7 @@
return resolve(
pathCodeOwnersResult.get().getPathCodeOwners(),
pathCodeOwnersResult.get().getAnnotations(),
+ pathCodeOwnersResult.get().resolvedImports(),
pathCodeOwnersResult.get().unresolvedImports(),
pathCodeOwnersResult.messages());
}
@@ -260,6 +260,7 @@
return resolve(
codeOwnerReferences,
/* annotationsByCodeOwnerReference= */ ImmutableMultimap.of(),
+ /* resolvedImports= */ ImmutableList.of(),
/* unresolvedImports= */ ImmutableList.of(),
/* pathCodeOwnersMessages= */ ImmutableList.of());
}
@@ -279,9 +280,11 @@
private CodeOwnerResolverResult resolve(
Set<CodeOwnerReference> codeOwnerReferences,
ImmutableMultimap<CodeOwnerReference, CodeOwnerAnnotation> annotationsByCodeOwnerReference,
- List<UnresolvedImport> unresolvedImports,
+ ImmutableList<CodeOwnerConfigImport> resolvedImports,
+ ImmutableList<CodeOwnerConfigImport> unresolvedImports,
ImmutableList<String> pathCodeOwnersMessages) {
requireNonNull(codeOwnerReferences, "codeOwnerReferences");
+ requireNonNull(resolvedImports, "resolvedImports");
requireNonNull(unresolvedImports, "unresolvedImports");
requireNonNull(pathCodeOwnersMessages, "pathCodeOwnersMessages");
@@ -313,7 +316,8 @@
annotationsByCodeOwner.build(),
ownedByAllUsers.get(),
hasUnresolvedCodeOwners.get(),
- !unresolvedImports.isEmpty(),
+ resolvedImports,
+ unresolvedImports,
messageBuilder.build());
logger.atFine().log("resolve result = %s", codeOwnerResolverResult);
return codeOwnerResolverResult;
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
index 7dd9e96..4db3a4c 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
@@ -52,8 +52,16 @@
/** Whether there are code owner references which couldn't be resolved. */
public abstract boolean hasUnresolvedCodeOwners();
+ /** Imports which were successfully resolved. */
+ public abstract ImmutableList<CodeOwnerConfigImport> resolvedImports();
+
+ /** Imports which couldn't be resolved. */
+ public abstract ImmutableList<CodeOwnerConfigImport> unresolvedImports();
+
/** Whether there are imports which couldn't be resolved. */
- public abstract boolean hasUnresolvedImports();
+ public boolean hasUnresolvedImports() {
+ return !unresolvedImports().isEmpty();
+ }
/** Gets messages that were collected while resolving the code owners. */
public abstract ImmutableList<String> messages();
@@ -76,7 +84,8 @@
.add("annotations", annotations())
.add("ownedByAllUsers", ownedByAllUsers())
.add("hasUnresolvedCodeOwners", hasUnresolvedCodeOwners())
- .add("hasUnresolvedImports", hasUnresolvedImports())
+ .add("resolvedImports", resolvedImports())
+ .add("unresolvedImports", unresolvedImports())
.add("messages", messages())
.toString();
}
@@ -87,25 +96,16 @@
ImmutableMultimap<CodeOwner, CodeOwnerAnnotation> annotations,
boolean ownedByAllUsers,
boolean hasUnresolvedCodeOwners,
- boolean hasUnresolvedImports,
+ ImmutableList<CodeOwnerConfigImport> resolvedImports,
+ ImmutableList<CodeOwnerConfigImport> unresolvedImports,
List<String> messages) {
return new AutoValue_CodeOwnerResolverResult(
codeOwners,
annotations,
ownedByAllUsers,
hasUnresolvedCodeOwners,
- hasUnresolvedImports,
+ resolvedImports,
+ unresolvedImports,
ImmutableList.copyOf(messages));
}
-
- /** Creates a empty {@link CodeOwnerResolverResult} instance. */
- public static CodeOwnerResolverResult createEmpty() {
- return new AutoValue_CodeOwnerResolverResult(
- /* codeOwners= */ ImmutableSet.of(),
- /* annotations= */ ImmutableMultimap.of(),
- /* ownedByAllUsers= */ false,
- /* hasUnresolvedCodeOwners= */ false,
- /* hasUnresolvedImports= */ false,
- /* messages= */ ImmutableList.of());
- }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index a61c913..5e7a11f 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -252,17 +252,18 @@
}
// Resolve global imports.
- ImmutableList.Builder<UnresolvedImport> unresolvedImports = ImmutableList.builder();
- ImmutableSet<CodeOwnerConfigImport> globalImports = getGlobalImports(0, codeOwnerConfig);
- OptionalResultWithMessages<List<UnresolvedImport>> unresolvedGlobalImports;
+ ImmutableList.Builder<CodeOwnerConfigImport> resolvedImports = ImmutableList.builder();
+ ImmutableList.Builder<CodeOwnerConfigImport> unresolvedImports = ImmutableList.builder();
+ ImmutableSet<CodeOwnerImport> globalImports = getGlobalImports(0, codeOwnerConfig);
+ OptionalResultWithMessages<CodeOwnerConfigImports> globalImportedCodeOwnerConfigs;
if (!globalCodeOwnersIgnored) {
- unresolvedGlobalImports =
+ globalImportedCodeOwnerConfigs =
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 =
+ globalImportedCodeOwnerConfigs =
resolveImports(
codeOwnerConfig.key(),
globalImports.stream()
@@ -273,8 +274,9 @@
.collect(toImmutableSet()),
resolvedCodeOwnerConfigBuilder);
}
- messages.addAll(unresolvedGlobalImports.messages());
- unresolvedImports.addAll(unresolvedGlobalImports.get());
+ messages.addAll(globalImportedCodeOwnerConfigs.messages());
+ resolvedImports.addAll(globalImportedCodeOwnerConfigs.get().resolved());
+ unresolvedImports.addAll(globalImportedCodeOwnerConfigs.get().unresolved());
// 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
@@ -317,18 +319,22 @@
}
// Resolve per-file imports.
- ImmutableSet<CodeOwnerConfigImport> perFileImports =
+ ImmutableSet<CodeOwnerImport> perFileImports =
getPerFileImports(
0, codeOwnerConfig.key(), resolvedCodeOwnerConfigBuilder.codeOwnerSets());
- OptionalResultWithMessages<List<UnresolvedImport>> unresolvedPerFileImports =
+ OptionalResultWithMessages<CodeOwnerConfigImports> perFileImportedCodeOwnerConfigs =
resolveImports(codeOwnerConfig.key(), perFileImports, resolvedCodeOwnerConfigBuilder);
- messages.addAll(unresolvedPerFileImports.messages());
- unresolvedImports.addAll(unresolvedPerFileImports.get());
+ messages.addAll(perFileImportedCodeOwnerConfigs.messages());
+ resolvedImports.addAll(perFileImportedCodeOwnerConfigs.get().resolved());
+ unresolvedImports.addAll(perFileImportedCodeOwnerConfigs.get().unresolved());
this.pathCodeOwnersResult =
OptionalResultWithMessages.create(
PathCodeOwnersResult.create(
- path, resolvedCodeOwnerConfigBuilder.build(), unresolvedImports.build()),
+ path,
+ resolvedCodeOwnerConfigBuilder.build(),
+ resolvedImports.build(),
+ unresolvedImports.build()),
messages);
logger.atFine().log("path code owners result = %s", pathCodeOwnersResult);
return this.pathCodeOwnersResult;
@@ -343,11 +349,12 @@
* @param resolvedCodeOwnerConfigBuilder the builder for the resolved code owner config
* @return list of unresolved imports, empty list if all imports were successfully resolved
*/
- private OptionalResultWithMessages<List<UnresolvedImport>> resolveImports(
+ private OptionalResultWithMessages<CodeOwnerConfigImports> resolveImports(
CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
- Set<CodeOwnerConfigImport> codeOwnerConfigImports,
+ Set<CodeOwnerImport> codeOwnerConfigImports,
CodeOwnerConfig.Builder resolvedCodeOwnerConfigBuilder) {
- ImmutableList.Builder<UnresolvedImport> unresolvedImports = ImmutableList.builder();
+ ImmutableList.Builder<CodeOwnerConfigImport> resolvedImports = ImmutableList.builder();
+ ImmutableList.Builder<CodeOwnerConfigImport> unresolvedImports = ImmutableList.builder();
StringBuilder messageBuilder = new StringBuilder();
try (Timer0.Context ctx = codeOwnerMetrics.resolveCodeOwnerConfigImports.start()) {
logger.atFine().log("resolve imports of codeOwnerConfig %s", keyOfImportingCodeOwnerConfig);
@@ -361,7 +368,7 @@
Map<BranchNameKey, ObjectId> revisionMap = new HashMap<>();
revisionMap.put(codeOwnerConfig.key().branchNameKey(), codeOwnerConfig.revision());
- Queue<CodeOwnerConfigImport> codeOwnerConfigsToImport = new ArrayDeque<>();
+ Queue<CodeOwnerImport> codeOwnerConfigsToImport = new ArrayDeque<>();
codeOwnerConfigsToImport.addAll(codeOwnerConfigImports);
if (!codeOwnerConfigsToImport.isEmpty()) {
messageBuilder.append(
@@ -370,7 +377,7 @@
keyOfImportingCodeOwnerConfig.format(codeOwners)));
}
while (!codeOwnerConfigsToImport.isEmpty()) {
- CodeOwnerConfigImport codeOwnerConfigImport = codeOwnerConfigsToImport.poll();
+ CodeOwnerImport codeOwnerConfigImport = codeOwnerConfigsToImport.poll();
messageBuilder.append(codeOwnerConfigImport.format());
CodeOwnerConfigReference codeOwnerConfigReference =
@@ -387,7 +394,7 @@
projectCache.get(keyOfImportedCodeOwnerConfig.project());
if (!projectState.isPresent()) {
unresolvedImports.add(
- UnresolvedImport.create(
+ CodeOwnerConfigImport.createUnresolvedImport(
codeOwnerConfigImport.importingCodeOwnerConfig(),
keyOfImportedCodeOwnerConfig,
codeOwnerConfigReference,
@@ -399,7 +406,7 @@
}
if (!projectState.get().statePermitsRead()) {
unresolvedImports.add(
- UnresolvedImport.create(
+ CodeOwnerConfigImport.createUnresolvedImport(
codeOwnerConfigImport.importingCodeOwnerConfig(),
keyOfImportedCodeOwnerConfig,
codeOwnerConfigReference,
@@ -425,7 +432,7 @@
if (!mayBeImportedCodeOwnerConfig.isPresent()) {
unresolvedImports.add(
- UnresolvedImport.create(
+ CodeOwnerConfigImport.createUnresolvedImport(
codeOwnerConfigImport.importingCodeOwnerConfig(),
keyOfImportedCodeOwnerConfig,
codeOwnerConfigReference,
@@ -439,6 +446,13 @@
}
CodeOwnerConfig importedCodeOwnerConfig = mayBeImportedCodeOwnerConfig.get();
+
+ resolvedImports.add(
+ CodeOwnerConfigImport.createResolvedImport(
+ codeOwnerConfigImport.importingCodeOwnerConfig(),
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+
CodeOwnerConfigImportMode importMode = codeOwnerConfigReference.importMode();
logger.atFine().log("import mode = %s", importMode.name());
@@ -475,7 +489,7 @@
if (importMode.resolveImportsOfImport()
&& seenCodeOwnerConfigs.add(keyOfImportedCodeOwnerConfig)) {
logger.atFine().log("resolve imports of imported code owner config");
- Set<CodeOwnerConfigImport> transitiveImports = new HashSet<>();
+ Set<CodeOwnerImport> transitiveImports = new HashSet<>();
transitiveImports.addAll(
getGlobalImports(codeOwnerConfigImport.importLevel() + 1, importedCodeOwnerConfig));
transitiveImports.addAll(
@@ -495,7 +509,7 @@
transitiveImports.stream()
.map(
codeOwnerCfgImport ->
- CodeOwnerConfigImport.create(
+ CodeOwnerImport.create(
codeOwnerCfgImport.importLevel(),
codeOwnerCfgImport.importingCodeOwnerConfig(),
CodeOwnerConfigReference.copyWithNewImportMode(
@@ -516,31 +530,31 @@
message = message.substring(0, message.length() - 1);
}
return OptionalResultWithMessages.create(
- unresolvedImports.build(),
+ CodeOwnerConfigImports.create(resolvedImports.build(), unresolvedImports.build()),
!message.isEmpty() ? ImmutableList.of(message) : ImmutableList.of());
}
- private ImmutableSet<CodeOwnerConfigImport> getGlobalImports(
+ private ImmutableSet<CodeOwnerImport> getGlobalImports(
int importLevel, CodeOwnerConfig codeOwnerConfig) {
return codeOwnerConfig.imports().stream()
.map(
codeOwnerConfigReference ->
- CodeOwnerConfigImport.create(
+ CodeOwnerImport.create(
importLevel, codeOwnerConfig.key(), codeOwnerConfigReference))
.collect(toImmutableSet());
}
- private ImmutableSet<CodeOwnerConfigImport> getPerFileImports(
+ private ImmutableSet<CodeOwnerImport> getPerFileImports(
int importLevel,
CodeOwnerConfig.Key importingCodeOwnerConfig,
Set<CodeOwnerSet> codeOwnerSets) {
- ImmutableSet.Builder<CodeOwnerConfigImport> codeOwnerConfigImports = ImmutableSet.builder();
+ ImmutableSet.Builder<CodeOwnerImport> codeOwnerConfigImports = ImmutableSet.builder();
for (CodeOwnerSet codeOwnerSet : codeOwnerSets) {
codeOwnerSet.imports().stream()
.forEach(
codeOwnerConfigReference ->
codeOwnerConfigImports.add(
- CodeOwnerConfigImport.create(
+ CodeOwnerImport.create(
importLevel,
importingCodeOwnerConfig,
codeOwnerConfigReference,
@@ -623,7 +637,7 @@
}
@AutoValue
- abstract static class CodeOwnerConfigImport {
+ abstract static class CodeOwnerImport {
/**
* The import level.
*
@@ -683,7 +697,7 @@
return levels > 0 ? String.format("%" + (levels * 2) + "s", "") : "";
}
- public static CodeOwnerConfigImport create(
+ public static CodeOwnerImport create(
int importLevel,
CodeOwnerConfig.Key importingCodeOwnerConfig,
CodeOwnerConfigReference codeOwnerConfigReference) {
@@ -691,7 +705,7 @@
importLevel, importingCodeOwnerConfig, codeOwnerConfigReference, Optional.empty());
}
- public static CodeOwnerConfigImport create(
+ public static CodeOwnerImport create(
int importLevel,
CodeOwnerConfig.Key importingCodeOwnerConfig,
CodeOwnerConfigReference codeOwnerConfigReference,
@@ -703,13 +717,28 @@
Optional.of(codeOwnerSet));
}
- public static CodeOwnerConfigImport create(
+ public static CodeOwnerImport create(
int importLevel,
CodeOwnerConfig.Key importingCodeOwnerConfig,
CodeOwnerConfigReference codeOwnerConfigReference,
Optional<CodeOwnerSet> codeOwnerSet) {
- return new AutoValue_PathCodeOwners_CodeOwnerConfigImport(
+ return new AutoValue_PathCodeOwners_CodeOwnerImport(
importLevel, importingCodeOwnerConfig, codeOwnerConfigReference, codeOwnerSet);
}
}
+
+ @AutoValue
+ abstract static class CodeOwnerConfigImports {
+ /** Imported code owner configs the could be resolved. */
+ abstract ImmutableList<CodeOwnerConfigImport> resolved();
+
+ /** Imported code owner configs the could not be resolved. */
+ abstract ImmutableList<CodeOwnerConfigImport> unresolved();
+
+ static CodeOwnerConfigImports create(
+ ImmutableList<CodeOwnerConfigImport> resolved,
+ ImmutableList<CodeOwnerConfigImport> unresolved) {
+ return new AutoValue_PathCodeOwners_CodeOwnerConfigImports(resolved, unresolved);
+ }
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
index 93a36be..5b262bf 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
@@ -36,8 +36,11 @@
/** Gets the resolved code owner config. */
abstract CodeOwnerConfig codeOwnerConfig();
+ /** Gets a list of resolved imports. */
+ public abstract ImmutableList<CodeOwnerConfigImport> resolvedImports();
+
/** Gets a list of unresolved imports. */
- public abstract ImmutableList<UnresolvedImport> unresolvedImports();
+ public abstract ImmutableList<CodeOwnerConfigImport> unresolvedImports();
/** Whether there are unresolved imports. */
public boolean hasUnresolvedImports() {
@@ -104,14 +107,21 @@
return MoreObjects.toStringHelper(this)
.add("path", path())
.add("codeOwnerConfig", codeOwnerConfig())
+ .add("resolvedImports", resolvedImports())
.add("unresolvedImports", unresolvedImports())
.toString();
}
/** Creates a {@link PathCodeOwnersResult} instance. */
public static PathCodeOwnersResult create(
- Path path, CodeOwnerConfig codeOwnerConfig, List<UnresolvedImport> unresolvedImports) {
+ Path path,
+ CodeOwnerConfig codeOwnerConfig,
+ List<CodeOwnerConfigImport> resolvedImports,
+ List<CodeOwnerConfigImport> unresolvedImports) {
return new AutoValue_PathCodeOwnersResult(
- path, codeOwnerConfig, ImmutableList.copyOf(unresolvedImports));
+ path,
+ codeOwnerConfig,
+ ImmutableList.copyOf(resolvedImports),
+ ImmutableList.copyOf(unresolvedImports));
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java b/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java
deleted file mode 100644
index bea1e08..0000000
--- a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.plugins.codeowners.backend;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.base.MoreObjects;
-
-/** Information about an unresolved import. */
-@AutoValue
-public abstract class UnresolvedImport {
- /** Key of the importing code owner config. */
- public abstract CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig();
-
- /** Key of the imported code owner config. */
- public abstract CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig();
-
- /** The code owner config reference that was attempted to be resolved. */
- public abstract CodeOwnerConfigReference codeOwnerConfigReference();
-
- /** Message explaining why the code owner config reference couldn't be resolved. */
- public abstract String message();
-
- @Override
- public final String toString() {
- return MoreObjects.toStringHelper(this)
- .add("keyOfImportingCodeOwnerConfig", keyOfImportingCodeOwnerConfig())
- .add("keyOfImportedCodeOwnerConfig", keyOfImportedCodeOwnerConfig())
- .add("codeOwnerConfigReference", codeOwnerConfigReference())
- .add("message", message())
- .toString();
- }
-
- /** Creates a {@link UnresolvedImport} instance. */
- static UnresolvedImport create(
- CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
- CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig,
- CodeOwnerConfigReference codeOwnerConfigReference,
- String message) {
- return new AutoValue_UnresolvedImport(
- keyOfImportingCodeOwnerConfig,
- keyOfImportedCodeOwnerConfig,
- codeOwnerConfigReference,
- message);
- }
-}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportFormatter.java b/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportFormatter.java
index 52958d9..7946468 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportFormatter.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportFormatter.java
@@ -20,7 +20,7 @@
import com.google.inject.Inject;
import java.nio.file.Path;
-/** Class to format an {@link UnresolvedImport} as a user-readable string. */
+/** Class to format an {@link CodeOwnerConfigImport} as a user-readable string. */
public class UnresolvedImportFormatter {
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final ProjectCache projectCache;
@@ -37,7 +37,7 @@
}
/** Returns a user-readable string representation of the given unresolved import. */
- public String format(UnresolvedImport unresolvedImport) {
+ public String format(CodeOwnerConfigImport unresolvedImport) {
return String.format(
"The import of %s:%s:%s in %s:%s:%s cannot be resolved: %s",
unresolvedImport.keyOfImportedCodeOwnerConfig().project(),
@@ -46,10 +46,16 @@
unresolvedImport.keyOfImportingCodeOwnerConfig().project(),
unresolvedImport.keyOfImportingCodeOwnerConfig().shortBranchName(),
getFilePath(unresolvedImport.keyOfImportingCodeOwnerConfig()),
- unresolvedImport.message());
+ unresolvedImport
+ .errorMessage()
+ .orElseThrow(
+ () ->
+ new IllegalStateException(
+ String.format(
+ "unresolved import %s must have an error message", unresolvedImport))));
}
- private Path getFilePath(CodeOwnerConfig.Key codeOwnerConfigKey) {
+ public Path getFilePath(CodeOwnerConfig.Key codeOwnerConfigKey) {
return getBackend(codeOwnerConfigKey).getFilePath(codeOwnerConfigKey);
}
@@ -60,6 +66,10 @@
* returned.
*/
private CodeOwnerBackend getBackend(CodeOwnerConfig.Key codeOwnerConfigKey) {
+ // For unresolved imports the project may not exist. Trying to get the project config for
+ // non-existing projects fails, hence check whether the project exists before trying to access
+ // the project config and fall back to the default code owner backend if the project doesn't
+ // exist.
if (projectCache.get(codeOwnerConfigKey.project()).isPresent()) {
return codeOwnersPluginConfiguration
.getProjectConfig(codeOwnerConfigKey.project())
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
index 677e67c..b918d5d 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
@@ -38,6 +38,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnersInfo;
import com.google.gerrit.plugins.codeowners.backend.CodeOwner;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotation;
@@ -92,6 +93,7 @@
private final CodeOwnerConfigHierarchy codeOwnerConfigHierarchy;
private final Provider<CodeOwnerResolver> codeOwnerResolver;
private final CodeOwnerJson.Factory codeOwnerJsonFactory;
+ private final CodeOwnerConfigFileJson codeOwnerConfigFileJson;
private final EnumSet<ListAccountsOption> options;
private final Set<String> hexOptions;
@@ -167,7 +169,8 @@
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
Provider<CodeOwnerResolver> codeOwnerResolver,
- CodeOwnerJson.Factory codeOwnerJsonFactory) {
+ CodeOwnerJson.Factory codeOwnerJsonFactory,
+ CodeOwnerConfigFileJson codeOwnerConfigFileJson) {
this.accountVisibility = accountVisibility;
this.accounts = accounts;
this.accountControlFactory = accountControlFactory;
@@ -178,6 +181,7 @@
this.codeOwnerConfigHierarchy = codeOwnerConfigHierarchy;
this.codeOwnerResolver = codeOwnerResolver;
this.codeOwnerJsonFactory = codeOwnerJsonFactory;
+ this.codeOwnerConfigFileJson = codeOwnerConfigFileJson;
this.options = EnumSet.noneOf(ListAccountsOption.class);
this.hexOptions = new HashSet<>();
}
@@ -213,6 +217,8 @@
ListMultimap<CodeOwner, CodeOwnerAnnotation> annotations = LinkedListMultimap.create();
AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
ImmutableList.Builder<String> debugLogsBuilder = ImmutableList.builder();
+ ImmutableList.Builder<CodeOwnerConfigFileInfo> codeOwnerConfigFileInfosBuilder =
+ ImmutableList.builder();
codeOwnerConfigHierarchy.visit(
rsrc.getBranch(),
rsrc.getRevision(),
@@ -221,6 +227,12 @@
CodeOwnerResolverResult pathCodeOwners =
codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, rsrc.getPath());
+ codeOwnerConfigFileInfosBuilder.add(
+ codeOwnerConfigFileJson.format(
+ codeOwnerConfig.key(),
+ pathCodeOwners.resolvedImports(),
+ pathCodeOwners.unresolvedImports()));
+
debugLogsBuilder.addAll(pathCodeOwners.messages());
codeOwners.addAll(pathCodeOwners.codeOwners());
annotations.putAll(pathCodeOwners.annotations());
@@ -325,7 +337,7 @@
codeOwnersInfo.codeOwners =
codeOwnerJsonFactory.create(getFillOptions()).format(sortedAndLimitedCodeOwners);
codeOwnersInfo.ownedByAllUsers = ownedByAllUsers.get() ? true : null;
-
+ codeOwnersInfo.codeOwnerConfigs = codeOwnerConfigFileInfosBuilder.build();
ImmutableList<String> debugLogs = debugLogsBuilder.build();
codeOwnersInfo.debugLogs = debug ? debugLogs : null;
logger.atFine().log("debug logs: %s", debugLogs);
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJson.java b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJson.java
new file mode 100644
index 0000000..fe17a3f
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJson.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.restapi;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImport;
+import com.google.gerrit.plugins.codeowners.backend.UnresolvedImportFormatter;
+import com.google.inject.Inject;
+import java.util.List;
+
+/** Collection of routines to populate {@link CodeOwnerConfigFileInfo}. */
+public class CodeOwnerConfigFileJson {
+ private final UnresolvedImportFormatter unresolvedImportFormatter;
+
+ @Inject
+ CodeOwnerConfigFileJson(UnresolvedImportFormatter unresolvedImportFormatter) {
+ this.unresolvedImportFormatter = unresolvedImportFormatter;
+ }
+
+ /**
+ * Formats the provided code owner config file information as a {@link CodeOwnerConfigFileInfo}.
+ *
+ * @param codeOwnerConfigKey the key of the code owner config file as {@link
+ * CodeOwnerConfigFileInfo}
+ * @param resolvedImports code owner config files which have been successfully imported directly
+ * or indirectly
+ * @param unresolvedImports code owner config files which are imported directly or indirectly but
+ * couldn't be resolved
+ * @return the provided {@link com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig.Key}
+ * as {@link CodeOwnerConfigFileInfo}
+ */
+ public CodeOwnerConfigFileInfo format(
+ CodeOwnerConfig.Key codeOwnerConfigKey,
+ List<CodeOwnerConfigImport> resolvedImports,
+ List<CodeOwnerConfigImport> unresolvedImports) {
+ requireNonNull(codeOwnerConfigKey, "codeOwnerConfigKey");
+ requireNonNull(resolvedImports, "resolvedImports");
+ requireNonNull(unresolvedImports, "unresolvedImports");
+
+ CodeOwnerConfigFileInfo info = new CodeOwnerConfigFileInfo();
+
+ info.project = codeOwnerConfigKey.project().get();
+ info.branch = codeOwnerConfigKey.branchNameKey().branch();
+ info.path = unresolvedImportFormatter.getFilePath(codeOwnerConfigKey).toString();
+
+ ImmutableList<CodeOwnerConfigFileInfo> unresolvedImportInfos =
+ unresolvedImports.stream()
+ .filter(
+ unresolvedImport ->
+ unresolvedImport.keyOfImportingCodeOwnerConfig().equals(codeOwnerConfigKey))
+ .map(
+ unresolvedImport -> {
+ CodeOwnerConfigFileInfo unresolvedCodeOwnerConfigFileInfo =
+ format(
+ unresolvedImport.keyOfImportedCodeOwnerConfig(),
+ /* resolvedImports= */ ImmutableList.of(),
+ /* unresolvedImports= */ ImmutableList.of());
+ unresolvedCodeOwnerConfigFileInfo.importMode =
+ unresolvedImport.codeOwnerConfigReference().importMode();
+ unresolvedCodeOwnerConfigFileInfo.unresolvedErrorMessage =
+ unresolvedImport
+ .errorMessage()
+ .orElseThrow(
+ () ->
+ new IllegalStateException(
+ String.format(
+ "unresolved import %s must have an error message",
+ unresolvedImport)));
+ return unresolvedCodeOwnerConfigFileInfo;
+ })
+ .collect(toImmutableList());
+ info.unresolvedImports = !unresolvedImportInfos.isEmpty() ? unresolvedImportInfos : null;
+
+ ImmutableList<CodeOwnerConfigFileInfo> importInfos =
+ resolvedImports.stream()
+ .filter(
+ resolvedImport ->
+ resolvedImport.keyOfImportingCodeOwnerConfig().equals(codeOwnerConfigKey))
+ .map(
+ resolvedImport -> {
+ CodeOwnerConfigFileInfo resolvedCodeOwnerConfigFileInfo =
+ format(
+ resolvedImport.keyOfImportedCodeOwnerConfig(),
+ removeImportEntriesFor(resolvedImports, codeOwnerConfigKey),
+ removeImportEntriesFor(unresolvedImports, codeOwnerConfigKey));
+ resolvedCodeOwnerConfigFileInfo.importMode =
+ resolvedImport.codeOwnerConfigReference().importMode();
+ return resolvedCodeOwnerConfigFileInfo;
+ })
+ .collect(toImmutableList());
+ info.imports = !importInfos.isEmpty() ? importInfos : null;
+
+ return info;
+ }
+
+ private ImmutableList<CodeOwnerConfigImport> removeImportEntriesFor(
+ List<CodeOwnerConfigImport> imports, CodeOwnerConfig.Key codeOwnerConfigKey) {
+ return imports.stream()
+ .filter(i -> !i.keyOfImportingCodeOwnerConfig().equals(codeOwnerConfigKey))
+ .collect(toImmutableList());
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
index e0a9145..85410e9 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
@@ -82,6 +82,7 @@
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
Provider<CodeOwnerResolver> codeOwnerResolver,
CodeOwnerJson.Factory codeOwnerJsonFactory,
+ CodeOwnerConfigFileJson codeOwnerConfigFileJson,
GitRepositoryManager repoManager) {
super(
accountVisibility,
@@ -93,7 +94,8 @@
codeOwnersPluginConfiguration,
codeOwnerConfigHierarchy,
codeOwnerResolver,
- codeOwnerJsonFactory);
+ codeOwnerJsonFactory,
+ codeOwnerConfigFileJson);
this.repoManager = repoManager;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
index 3ed9a51..b7149c5 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
@@ -73,7 +73,8 @@
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
Provider<CodeOwnerResolver> codeOwnerResolver,
ServiceUserClassifier serviceUserClassifier,
- CodeOwnerJson.Factory codeOwnerJsonFactory) {
+ CodeOwnerJson.Factory codeOwnerJsonFactory,
+ CodeOwnerConfigFileJson codeOwnerConfigFileJson) {
super(
accountVisibility,
accounts,
@@ -84,7 +85,8 @@
codeOwnersPluginConfiguration,
codeOwnerConfigHierarchy,
codeOwnerResolver,
- codeOwnerJsonFactory);
+ codeOwnerJsonFactory,
+ codeOwnerConfigFileJson);
this.serviceUserClassifier = serviceUserClassifier;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
new file mode 100644
index 0000000..410ad9a
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
@@ -0,0 +1,177 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.truth.ListSubject.elements;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
+import com.google.gerrit.truth.ListSubject;
+
+/** {@link Subject} for doing assertions on {@link CodeOwnerConfigFileInfo}s. */
+public class CodeOwnerConfigFileInfoSubject extends Subject {
+ /**
+ * Starts fluent chain to do assertions on a {@link CodeOwnerConfigFileInfo}.
+ *
+ * @param codeOwnerConfigFileInfo the code owner config file info on which assertions should be
+ * done
+ * @return the created {@link CodeOwnerConfigFileInfoSubject}
+ */
+ public static CodeOwnerConfigFileInfoSubject assertThat(
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo) {
+ return assertAbout(codeOwnerConfigFileInfos()).that(codeOwnerConfigFileInfo);
+ }
+
+ public static Factory<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo>
+ codeOwnerConfigFileInfos() {
+ return CodeOwnerConfigFileInfoSubject::new;
+ }
+
+ private final CodeOwnerConfigFileInfo codeOwnerConfigFileInfo;
+
+ private CodeOwnerConfigFileInfoSubject(
+ FailureMetadata metadata, CodeOwnerConfigFileInfo codeOwnerConfigFileInfo) {
+ super(metadata, codeOwnerConfigFileInfo);
+ this.codeOwnerConfigFileInfo = codeOwnerConfigFileInfo;
+ }
+
+ /** Returns a subject for the project of the code owner config file info. */
+ public StringSubject hasProjectThat() {
+ return check("project()").that(codeOwnerConfigFileInfo().project);
+ }
+
+ /** Returns a subject for the branch of the code owner config file info. */
+ public StringSubject hasBranchThat() {
+ return check("branch()").that(codeOwnerConfigFileInfo().branch);
+ }
+
+ /** Returns a subject for the path of the code owner config file info. */
+ public StringSubject hasPathThat() {
+ return check("path()").that(codeOwnerConfigFileInfo().path);
+ }
+
+ /**
+ * Returns a {@link ListSubject} for the (resolved) imports of the code owner config file info.
+ */
+ public ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo> hasImportsThat() {
+ return check("imports()")
+ .about(elements())
+ .thatCustom(codeOwnerConfigFileInfo().imports, codeOwnerConfigFileInfos());
+ }
+
+ /**
+ * Returns a {@link ListSubject} for the unresolved imports of the code owner config file info.
+ */
+ public ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo>
+ hasUnresolvedImportsThat() {
+ return check("unresolvedimports()")
+ .about(elements())
+ .thatCustom(codeOwnerConfigFileInfo().unresolvedImports, codeOwnerConfigFileInfos());
+ }
+
+ /** Returns a subject for the import mode of the code owner config file info. */
+ public Subject hasImportModeThat() {
+ return check("importMode()").that(codeOwnerConfigFileInfo().importMode);
+ }
+
+ /** Returns a subject for the unresolved error message of the code owner config file info. */
+ public Subject hasUnresolvedErrorMessageThat() {
+ return check("unresolvedErrorMessage()").that(codeOwnerConfigFileInfo().unresolvedErrorMessage);
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertKey(
+ CodeOwnerBackend codeOwnerBackend, CodeOwnerConfig.Key codeOwnerConfigKey) {
+ hasProjectThat().isEqualTo(codeOwnerConfigKey.project().get());
+ hasBranchThat().isEqualTo(codeOwnerConfigKey.branchNameKey().branch());
+ hasPathThat().isEqualTo(codeOwnerBackend.getFilePath(codeOwnerConfigKey).toString());
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertNoResolvedImports() {
+ hasImportsThat().isNull();
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertResolvedImport(
+ CodeOwnerBackend codeOwnerBackend,
+ CodeOwnerConfig.Key codeOwnerConfigKey,
+ CodeOwnerConfigImportMode importMode) {
+ hasImportsThat().hasSize(1);
+ CodeOwnerConfigFileInfoSubject subjectForResolvedImport = hasImportsThat().element(0);
+ subjectForResolvedImport
+ .assertKey(codeOwnerBackend, codeOwnerConfigKey)
+ .assertImportMode(importMode)
+ .assertNoUnresolvedErrorMessage();
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertNoUnresolvedImports() {
+ hasUnresolvedImportsThat().isNull();
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertUnresolvedImport(
+ CodeOwnerBackend codeOwnerBackend,
+ CodeOwnerConfig.Key codeOwnerConfigKey,
+ CodeOwnerConfigImportMode importMode,
+ String unresolvedErrorMessage) {
+ hasUnresolvedImportsThat().hasSize(1);
+ CodeOwnerConfigFileInfoSubject subjectForUnresolvedImport =
+ hasUnresolvedImportsThat().element(0);
+ subjectForUnresolvedImport
+ .assertKey(codeOwnerBackend, codeOwnerConfigKey)
+ .assertImportMode(importMode)
+ .assertUnresolvedErrorMessage(unresolvedErrorMessage);
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertNoImports() {
+ assertNoResolvedImports();
+ assertNoUnresolvedImports();
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertImportMode(CodeOwnerConfigImportMode importMode) {
+ hasImportModeThat().isEqualTo(importMode);
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertNoImportMode() {
+ hasImportModeThat().isNull();
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertUnresolvedErrorMessage(
+ String unresolvedErrorMessage) {
+ hasUnresolvedErrorMessageThat().isEqualTo(unresolvedErrorMessage);
+ return this;
+ }
+
+ public CodeOwnerConfigFileInfoSubject assertNoUnresolvedErrorMessage() {
+ hasUnresolvedErrorMessageThat().isNull();
+ return this;
+ }
+
+ private CodeOwnerConfigFileInfo codeOwnerConfigFileInfo() {
+ isNotNull();
+ return codeOwnerConfigFileInfo;
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
index 11433e2..9e5fb44 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
@@ -15,6 +15,7 @@
package com.google.gerrit.plugins.codeowners.testing;
import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject.codeOwnerConfigFileInfos;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerInfoSubject.codeOwnerInfos;
import static com.google.gerrit.truth.ListSubject.elements;
@@ -22,6 +23,7 @@
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.Subject;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnersInfo;
import com.google.gerrit.truth.ListSubject;
@@ -65,6 +67,13 @@
}
}
+ public ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo>
+ hasCodeOwnerConfigsThat() {
+ return check("codeOwnerConfigs()")
+ .about(elements())
+ .thatCustom(codeOwnersInfo().codeOwnerConfigs, codeOwnerConfigFileInfos());
+ }
+
public IterableSubject hasDebugLogsThat() {
return check("debugLogs").that(codeOwnersInfo().debugLogs);
}
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 74c9eb2..cd6f9a2 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject.assertThat;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerInfoSubject.hasAccountId;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerInfoSubject.hasAccountName;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnersInfoSubject.assertThat;
@@ -25,6 +26,7 @@
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
@@ -44,13 +46,16 @@
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestCodeOwnerConfigCreation;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestPathExpressions;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwners;
import com.google.gerrit.plugins.codeowners.api.CodeOwnersInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerSet;
+import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
import com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerCapability;
import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnersForPathInBranch;
import com.google.inject.Inject;
@@ -77,11 +82,14 @@
@Inject private GroupOperations groupOperations;
@Inject private ProjectOperations projectOperations;
+ private CodeOwnerBackend backend;
+
protected TestPathExpressions testPathExpressions;
@Before
public void setup() throws Exception {
testPathExpressions = plugin.getSysInjector().getInstance(TestPathExpressions.class);
+ backend = plugin.getSysInjector().getInstance(BackendConfig.class).getDefaultBackend();
}
/** Must return the {@link CodeOwners} API against which the tests should be run. */
@@ -98,7 +106,10 @@
@Test
public void getCodeOwnersWhenNoCodeOwnerConfigsExist() throws Exception {
- assertThat(queryCodeOwners("/foo/bar/baz.md")).hasCodeOwnersThat().isEmpty();
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners("/foo/bar/baz.md");
+ assertThat(codeOwnersInfo).hasCodeOwnersThat().isEmpty();
+ assertThat(codeOwnersInfo).hasOwnedByAllUsersThat().isNull();
+ assertThat(codeOwnersInfo).hasCodeOwnerConfigsThat().isEmpty();
}
@Test
@@ -115,6 +126,7 @@
CodeOwnersInfo codeOwnersInfo = queryCodeOwners("/foo/bar/baz.md");
assertThat(codeOwnersInfo).hasCodeOwnersThat().isEmpty();
assertThat(codeOwnersInfo).hasOwnedByAllUsersThat().isNull();
+ assertThat(codeOwnersInfo).hasCodeOwnerConfigsThat().isEmpty();
}
@Test
@@ -136,29 +148,32 @@
private void testGetCodeOwners(boolean useAbsolutePath) throws Exception {
TestAccount user2 = accountCreator.user2();
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/")
- .addCodeOwnerEmail(admin.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(user.email())
+ .create();
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/bar/")
- .addCodeOwnerEmail(user2.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey3 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(user2.email())
+ .create();
CodeOwnersInfo codeOwnersInfo =
queryCodeOwners(useAbsolutePath ? "/foo/bar/baz.md" : "foo/bar/baz.md");
@@ -172,6 +187,27 @@
.comparingElementsUsing(hasAccountName())
.containsExactly(null, null, null);
assertThat(codeOwnersInfo).hasOwnedByAllUsersThat().isNull();
+
+ assertThat(codeOwnersInfo.codeOwnerConfigs).hasSize(3);
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo3 = codeOwnersInfo.codeOwnerConfigs.get(0);
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo2 = codeOwnersInfo.codeOwnerConfigs.get(1);
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo1 = codeOwnersInfo.codeOwnerConfigs.get(2);
+ assertThat(codeOwnerConfigFileInfo3)
+ .assertKey(backend, codeOwnerConfigKey3)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnerConfigFileInfo2)
+ .assertKey(backend, codeOwnerConfigKey2)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnerConfigFileInfo1)
+ .assertKey(backend, codeOwnerConfigKey1)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+
assertThat(codeOwnersInfo).hasDebugLogsThat().isNull();
}
@@ -184,24 +220,26 @@
// 1. code owner config that makes "user2" a code owner, inheriting code owners from parent code
// owner configs is enabled by default
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/bar/")
- .addCodeOwnerEmail(user2.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(user2.email())
+ .create();
// 2. code owner config that makes "user" a code owner, code owners from parent code owner
// configs are ignored
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/")
- .ignoreParentCodeOwners()
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .ignoreParentCodeOwners()
+ .addCodeOwnerEmail(user.email())
+ .create();
// 3. code owner config that makes "admin" a code owner and assigns code ownership to all users,
// but for this test this code owner config is ignored, since the 2. code owner config ignores
@@ -227,36 +265,71 @@
.containsExactly(user2.id(), user.id())
.inOrder();
assertThat(codeOwnersInfo).hasOwnedByAllUsersThat().isNull();
+
+ assertThat(codeOwnersInfo.codeOwnerConfigs).hasSize(2);
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo1 = codeOwnersInfo.codeOwnerConfigs.get(0);
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo2 = codeOwnersInfo.codeOwnerConfigs.get(1);
+ assertThat(codeOwnerConfigFileInfo1)
+ .assertKey(backend, codeOwnerConfigKey1)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnerConfigFileInfo2)
+ .assertKey(backend, codeOwnerConfigKey2)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
}
@Test
public void getPerFileCodeOwners() throws Exception {
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression(testPathExpressions.matchFileType("txt"))
- .addCodeOwnerEmail(admin.email())
- .build())
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression(testPathExpressions.matchFileType("md"))
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("txt"))
+ .addCodeOwnerEmail(admin.email())
+ .build())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("md"))
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
- assertThat(queryCodeOwners("/foo/bar/config.txt"))
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners("/foo/bar/config.txt");
+ assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(admin.id());
- assertThat(queryCodeOwners("/foo/bar/baz.md"))
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, codeOwnerConfigKey)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+
+ codeOwnersInfo = queryCodeOwners("/foo/bar/baz.md");
+ assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(user.id());
- assertThat(queryCodeOwners("/foo/bar/main.config")).hasCodeOwnersThat().isEmpty();
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, codeOwnerConfigKey)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+
+ codeOwnersInfo = queryCodeOwners("/foo/bar/main.config");
+ assertThat(codeOwnersInfo).hasCodeOwnersThat().isEmpty();
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, codeOwnerConfigKey)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
}
@Test
@@ -548,22 +621,24 @@
TestAccount user2 = accountCreator.user2();
// create some code owner configs
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/")
- .addCodeOwnerEmail(admin.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/bar/")
- .addCodeOwnerEmail(user.email())
- .addCodeOwnerEmail(user2.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(user2.email())
+ .create();
// get code owners with different limits
CodeOwnersInfo codeOwnersInfo =
@@ -576,18 +651,51 @@
.element(0)
.hasAccountIdThat()
.isAnyOf(user.id(), user2.id());
+ assertThat(codeOwnersInfo.codeOwnerConfigs).hasSize(2);
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(0))
+ .assertKey(backend, codeOwnerConfigKey2)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(1))
+ .assertKey(backend, codeOwnerConfigKey1)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
codeOwnersInfo = queryCodeOwners(getCodeOwnersApi().query().withLimit(2), "/foo/bar/baz.md");
assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(user.id(), user2.id());
+ assertThat(codeOwnersInfo.codeOwnerConfigs).hasSize(2);
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(0))
+ .assertKey(backend, codeOwnerConfigKey2)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(1))
+ .assertKey(backend, codeOwnerConfigKey1)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
codeOwnersInfo = getCodeOwnersApi().query().withLimit(3).get("/foo/bar/baz.md");
assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(admin.id(), user.id(), user2.id());
+ assertThat(codeOwnersInfo.codeOwnerConfigs).hasSize(2);
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(0))
+ .assertKey(backend, codeOwnerConfigKey2)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
+ assertThat(codeOwnersInfo.codeOwnerConfigs.get(1))
+ .assertKey(backend, codeOwnerConfigKey1)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
}
@Test
@@ -632,10 +740,12 @@
public void getGlobalCodeOwners() throws Exception {
TestAccount globalOwner =
accountCreator.create("global_owner", "global.owner@example.com", "Global Owner", null);
- assertThat(queryCodeOwners("/foo/bar/baz.md"))
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners("/foo/bar/baz.md");
+ assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(globalOwner.id());
+ assertThat(codeOwnersInfo).hasCodeOwnerConfigsThat().isEmpty();
}
@Test
@@ -767,18 +877,25 @@
@Test
public void getDefaultCodeOwners() throws Exception {
// Create default code owner config file in refs/meta/config.
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch(RefNames.REFS_CONFIG)
- .folderPath("/")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch(RefNames.REFS_CONFIG)
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .create();
- assertThat(queryCodeOwners("/foo/bar/baz.md"))
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners("/foo/bar/baz.md");
+ assertThat(codeOwnersInfo)
.hasCodeOwnersThat()
.comparingElementsUsing(hasAccountId())
.containsExactly(user.id());
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, codeOwnerConfigKey)
+ .assertNoImports()
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage();
}
@Test
@@ -1643,4 +1760,168 @@
}
assertThat(foundDifferentOrder).isTrue();
}
+
+ @Test
+ public void getCodeOwnersWithUnresolvedImport() throws Exception {
+ skipTestIfImportsNotSupportedByCodeOwnersBackend();
+
+ Project.NameKey nonExistingProject = Project.nameKey("non-existing");
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(nonExistingProject, "master", "/", "OWNERS");
+ CodeOwnerConfigReference nonResolvableCodeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addImport(nonResolvableCodeOwnerConfigReference)
+ .addCodeOwnerEmail(admin.email())
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ String path = "/foo/bar/baz.md";
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners(getCodeOwnersApi().query(), path);
+ assertThat(codeOwnersInfo)
+ .hasCodeOwnersThat()
+ .comparingElementsUsing(hasAccountId())
+ .containsExactly(user.id(), admin.id());
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, keyOfImportingCodeOwnerConfig)
+ .assertNoResolvedImports()
+ .assertUnresolvedImport(
+ backend,
+ keyOfImportedCodeOwnerConfig,
+ nonResolvableCodeOwnerConfigReference.importMode(),
+ String.format("project %s not found", nonExistingProject));
+ }
+
+ @Test
+ public void getCodeOwnersWithResolvedImport() throws Exception {
+ skipTestIfImportsNotSupportedByCodeOwnersBackend();
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addImport(codeOwnerConfigReference)
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ String path = "/foo/bar/baz.md";
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners(getCodeOwnersApi().query(), path);
+ assertThat(codeOwnersInfo)
+ .hasCodeOwnersThat()
+ .comparingElementsUsing(hasAccountId())
+ .containsExactly(user.id(), admin.id());
+ assertThat(Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs))
+ .assertKey(backend, keyOfImportingCodeOwnerConfig)
+ .assertNoUnresolvedImports()
+ .assertResolvedImport(
+ backend, keyOfImportedCodeOwnerConfig, codeOwnerConfigReference.importMode());
+ }
+
+ @Test
+ public void getCodeOwnersWithNestedImport() throws Exception {
+ skipTestIfImportsNotSupportedByCodeOwnersBackend();
+
+ TestAccount user2 = accountCreator.user2();
+
+ Project.NameKey nonExistingProject = Project.nameKey("non-existing");
+ CodeOwnerConfig.Key keyOfUnresolvableCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(nonExistingProject, "master", "/", "OWNERS");
+ CodeOwnerConfigReference unresolvableCodeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ keyOfUnresolvableCodeOwnerConfig);
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .addImport(unresolvableCodeOwnerConfigReference)
+ .addCodeOwnerEmail(admin.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig2);
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .addImport(codeOwnerConfigReference2)
+ .addCodeOwnerEmail(user2.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig1);
+
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addImport(codeOwnerConfigReference1)
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ String path = "/foo/bar/baz.md";
+ CodeOwnersInfo codeOwnersInfo = queryCodeOwners(getCodeOwnersApi().query(), path);
+ assertThat(codeOwnersInfo)
+ .hasCodeOwnersThat()
+ .comparingElementsUsing(hasAccountId())
+ .containsExactly(user.id(), user2.id(), admin.id());
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ Iterables.getOnlyElement(codeOwnersInfo.codeOwnerConfigs);
+ assertThat(codeOwnerConfigFileInfo)
+ .assertKey(backend, keyOfImportingCodeOwnerConfig)
+ .assertNoImportMode()
+ .assertNoUnresolvedErrorMessage()
+ .assertNoUnresolvedImports()
+ .assertResolvedImport(
+ backend, keyOfImportedCodeOwnerConfig1, codeOwnerConfigReference1.importMode());
+
+ codeOwnerConfigFileInfo = Iterables.getOnlyElement(codeOwnerConfigFileInfo.imports);
+ assertThat(codeOwnerConfigFileInfo)
+ .assertKey(backend, keyOfImportedCodeOwnerConfig1)
+ .assertImportMode(codeOwnerConfigReference1.importMode())
+ .assertNoUnresolvedErrorMessage()
+ .assertNoUnresolvedImports()
+ .assertResolvedImport(
+ backend, keyOfImportedCodeOwnerConfig2, codeOwnerConfigReference2.importMode());
+
+ codeOwnerConfigFileInfo = Iterables.getOnlyElement(codeOwnerConfigFileInfo.imports);
+ assertThat(codeOwnerConfigFileInfo)
+ .assertKey(backend, keyOfImportedCodeOwnerConfig2)
+ .assertImportMode(codeOwnerConfigReference2.importMode())
+ .assertNoUnresolvedErrorMessage()
+ .assertNoResolvedImports()
+ .assertUnresolvedImport(
+ backend,
+ keyOfUnresolvableCodeOwnerConfig,
+ unresolvableCodeOwnerConfigReference.importMode(),
+ String.format("project %s not found", nonExistingProject));
+ }
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportTest.java
new file mode 100644
index 0000000..b6efbc6
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportTest.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.backend;
+
+import org.junit.Test;
+
+/** Tests for {@link CodeOwnerConfigImport}. */
+public class CodeOwnerConfigImportTest extends AbstractAutoValueTest {
+ @Test
+ public void toStringIncludesAllData_resolvedImport() throws Exception {
+ CodeOwnerConfigImport resolvedImport =
+ CodeOwnerConfigImport.createResolvedImport(
+ CodeOwnerConfig.Key.create(project, "master", "/"),
+ CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"));
+ assertThatToStringIncludesAllData(resolvedImport, CodeOwnerConfigImport.class);
+ }
+
+ @Test
+ public void toStringIncludesAllData_unresolvedImport() throws Exception {
+ CodeOwnerConfigImport unresolvedImport =
+ CodeOwnerConfigImport.createUnresolvedImport(
+ CodeOwnerConfig.Key.create(project, "master", "/"),
+ CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"),
+ "test message");
+ assertThatToStringIncludesAllData(unresolvedImport, CodeOwnerConfigImport.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
index 18ea017..deab817 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
@@ -29,7 +29,17 @@
/* annotations= */ ImmutableMultimap.of(),
/* ownedByAllUsers= */ false,
/* hasUnresolvedCodeOwners= */ false,
- /* hasUnresolvedImports= */ false,
+ ImmutableList.of(
+ CodeOwnerConfigImport.createResolvedImport(
+ CodeOwnerConfig.Key.create(project, "master", "/"),
+ CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))),
+ ImmutableList.of(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ CodeOwnerConfig.Key.create(project, "master", "/"),
+ CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"),
+ "test message")),
ImmutableList.of("test message"));
assertThatToStringIncludesAllData(codeOwnerResolverResult, CodeOwnerResolverResult.class);
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java
index d270fdc..d9b3a71 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java
@@ -27,19 +27,27 @@
@Test
public void toStringIncludesAllData() throws Exception {
CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/");
- CodeOwnerConfigReference codeOwnerConfigReference =
+ CodeOwnerConfigReference resolvableCodeOwnerConfigReference =
CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS");
+ CodeOwnerConfigReference unresolvableCodeOwnerConfigReference =
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/baz/OWNERS");
PathCodeOwnersResult pathCodeOwnersResult =
PathCodeOwnersResult.create(
Paths.get("/foo/bar/baz.md"),
CodeOwnerConfig.builder(codeOwnerConfigKey, TEST_REVISION)
- .addImport(codeOwnerConfigReference)
+ .addImport(resolvableCodeOwnerConfigReference)
+ .addImport(unresolvableCodeOwnerConfigReference)
.build(),
ImmutableList.of(
- UnresolvedImport.create(
+ CodeOwnerConfigImport.createResolvedImport(
codeOwnerConfigKey,
CodeOwnerConfig.Key.create(project, "master", "/bar/"),
- codeOwnerConfigReference,
+ resolvableCodeOwnerConfigReference)),
+ ImmutableList.of(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ codeOwnerConfigKey,
+ CodeOwnerConfig.Key.create(project, "master", "/baz/"),
+ unresolvableCodeOwnerConfigReference,
"test message")));
assertThatToStringIncludesAllData(pathCodeOwnersResult, PathCodeOwnersResult.class);
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
index 9563ade..04df442 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
@@ -239,7 +239,8 @@
pathCodeOwnersFactory.createWithoutCache(
emptyCodeOwnerConfig, Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners.resolveCodeOwnerConfig().get().getPathCodeOwners()).isEmpty();
- assertThat(pathCodeOwners.resolveCodeOwnerConfig().get().hasUnresolvedImports()).isFalse();
+ assertThat(pathCodeOwners.resolveCodeOwnerConfig().get().resolvedImports()).isEmpty();
+ assertThat(pathCodeOwners.resolveCodeOwnerConfig().get().unresolvedImports()).isEmpty();
}
@Test
@@ -471,6 +472,11 @@
@Test
public void nonResolveableImportIsIgnored() throws Exception {
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(project, "master", "/non-existing/", "OWNERS");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with non-resolveable import
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -478,9 +484,7 @@
.project(project)
.branch("master")
.folderPath("/")
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.ALL, "/non-existing/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(admin.email()).build())
.create();
@@ -494,24 +498,40 @@
// Expectation: we get the global code owner from the importing code owner config, the
// non-resolveable import is silently ignored
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format(
+ "code owner config does not exist (revision = %s)",
+ projectOperations
+ .project(keyOfImportedCodeOwnerConfig.project())
+ .getHead(keyOfImportedCodeOwnerConfig.branchNameKey().branch())
+ .name())));
}
@Test
public void importOfNonCodeOwnerConfigFileIsIgnored() throws Exception {
// create a file that looks like a code owner config file, but which has a name that is not
// allowed as code owner config file
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/")
- .fileName("FOO")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .fileName("FOO")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
// create config with import of non code owner config file
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
@@ -520,7 +540,7 @@
.project(project)
.branch("master")
.folderPath("/")
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/FOO"))
+ .addImport(codeOwnerConfigReference)
.addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(admin.email()).build())
.create();
@@ -538,7 +558,19 @@
assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwnersResult.hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format(
+ "code owner config does not exist (revision = %s)",
+ projectOperations
+ .project(keyOfImportedCodeOwnerConfig.project())
+ .getHead(keyOfImportedCodeOwnerConfig.branchNameKey().branch())
+ .name())));
}
@Test
@@ -548,14 +580,17 @@
// parameter fileExtension) or file extensions are enabled for code owner config files (config
// paramater enableCodeOwnerConfigFilesWithFileExtensions). Both is not the case here, hence any
// import of this file in another code owner config file should get ignored.
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/")
- .fileName("OWNERS.foo")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .fileName("OWNERS.foo")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
// create the importing config
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
@@ -564,8 +599,7 @@
.project(project)
.branch("master")
.folderPath("/")
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/OWNERS.FOO"))
+ .addImport(codeOwnerConfigReference)
.addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(admin.email()).build())
.create();
@@ -584,7 +618,19 @@
assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwnersResult.hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format(
+ "code owner config does not exist (revision = %s)",
+ projectOperations
+ .project(keyOfImportedCodeOwnerConfig.project())
+ .getHead(keyOfImportedCodeOwnerConfig.branchNameKey().branch())
+ .name())));
}
@Test
@@ -595,14 +641,17 @@
// Create a code owner config file with a file extension. This file is considered as a code
// owner config file since file extensions for code owner config files are enabled (paramater
// enableCodeOwnerConfigFilesWithFileExtensions).
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/")
- .fileName("OWNERS.FOO")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .fileName("OWNERS.FOO")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
// create the importing config
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
@@ -611,8 +660,7 @@
.project(project)
.branch("master")
.folderPath("/")
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/OWNERS.FOO"))
+ .addImport(codeOwnerConfigReference)
.addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(admin.email()).build())
.create();
@@ -630,7 +678,13 @@
assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwnersResult.hasUnresolvedImports()).isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -644,6 +698,19 @@
}
private void testImportGlobalCodeOwners(CodeOwnerConfigImportMode importMode) throws Exception {
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(importMode, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -652,18 +719,9 @@
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(CodeOwnerConfigReference.create(importMode, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -674,17 +732,40 @@
// Expectation: we get the global code owners from the importing and the imported code owner
// config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void importPerFileCodeOwners_importModeAll() throws Exception {
+ // create imported config with matching per-file code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("*.md")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with matching per-file code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
@@ -695,44 +776,53 @@
.addPathExpression("*.md")
.addCodeOwnerEmail(admin.email())
.build())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with matching per-file code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("*.md")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the matching per-file code owners from the importing and the imported
// code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void nonMatchingPerFileCodeOwnersAreNotImported_importModeAll() throws Exception {
+ // create imported config with non-matching per-file code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("*.txt")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with matching per-file code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
@@ -743,27 +833,13 @@
.addPathExpression("*.md")
.addCodeOwnerEmail(admin.email())
.build())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with non-matching per-file code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("*.txt")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -771,17 +847,41 @@
// Expectation: we only get the matching per-file code owners from the importing code owner
// config, the per-file code owners from the imported code owner config are not relevant since
// they do not match
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void perFileCodeOwnersAreNotImported_importModeGlobalCodeOwnerSetsOnly() throws Exception {
+ // create imported config with matching per-file code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("*.md")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config with matching per-file code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
@@ -792,28 +892,13 @@
.addPathExpression("*.md")
.addCodeOwnerEmail(admin.email())
.build())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with matching per-file code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("*.md")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -821,48 +906,57 @@
// Expectation: we only get the matching per-file code owners from the importing code owner
// config, the matching per-file code owners from the imported code owner config are not
// relevant with import mode GLOBAL_CODE_OWNER_SETS_ONLY
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void
importIgnoreGlobalAndParentCodeOwnersFlagFromMatchingPerFileCodeOwnerSet_importModeAll()
throws Exception {
+ // create imported config with matching per-file code owner that has the
+ // ignoreGlobalAndParentCodeOwners flag set to true
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .setIgnoreGlobalAndParentCodeOwners()
+ .addPathExpression("*.md")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with matching per-file code owner that has the
- // ignoreGlobalAndParentCodeOwners flag set to true
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .setIgnoreGlobalAndParentCodeOwners()
- .addPathExpression("*.md")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -872,50 +966,58 @@
// the matching per-file code owner set in the imported code owner config has the
// ignoreGlobalAndParentCodeOwners flag set to true which causes global code owners to be
// ignored, in addition this flag causes parent code owners to be ignored
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isTrue();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void
ignoreGlobalAndParentCodeOwnersFlagIsNotImportedFromNonMatchingPerFileCodeOwnerSet_importModeAll()
throws Exception {
+ // create imported config with non-matching per-file code owner that has the
+ // ignoreGlobalAndParentCodeOwners flag set to true
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .setIgnoreGlobalAndParentCodeOwners()
+ .addPathExpression("*.txt")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with non-matching per-file code owner that has the
- // ignoreGlobalAndParentCodeOwners flag set to true
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .setIgnoreGlobalAndParentCodeOwners()
- .addPathExpression("*.txt")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -924,50 +1026,58 @@
// per-file code owners from the imported code owner config and its
// ignoreGlobalAndParentCodeOwners flag are not relevant since the per-file code owner set does
// not match
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isFalse();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void ignoreGlobalAndParentCodeOwnersFlagIsNotImported_importModeGlobalCodeOwnerSetsOnly()
throws Exception {
+ // create imported config with matching per-file code owner that has the
+ // ignoreGlobalAndParentCodeOwners flag set to true
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .setIgnoreGlobalAndParentCodeOwners()
+ .addPathExpression("*.md")
+ .addCodeOwnerEmail(user.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with matching per-file code owner that has the
- // ignoreGlobalAndParentCodeOwners flag set to true
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .setIgnoreGlobalAndParentCodeOwners()
- .addPathExpression("*.md")
- .addCodeOwnerEmail(user.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -976,89 +1086,114 @@
// matching per-file code owners from the imported code owner config and its
// ignoreGlobalAndParentCodeOwners flag are not relevant with import mode
// GLOBAL_CODE_OWNER_SETS_ONLY
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isFalse();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void importIgnoreParentCodeOwnersFlag_importModeAll() throws Exception {
+ // create imported config with the ignoreParentCodeOnwers flag set to true
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .ignoreParentCodeOwners()
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with the ignoreParentCodeOnwers flag set to true
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .ignoreParentCodeOwners()
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: ignoreParentCodeOwners is true because the ignoreParentCodeOwners flag in the
// imported code owner config is set to true
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isTrue();
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void ignoreParentCodeOwnersFlagNotImported_importModeGlobalCodeOwnerSetsOnly()
throws Exception {
+ // create imported config with the ignoreParentCodeOnwers flag set to true
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .ignoreParentCodeOwners()
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with the ignoreParentCodeOnwers flag set to true
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .ignoreParentCodeOwners()
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: ignoreParentCodeOwners is false because the ignoreParentCodeOwners flag in the
// imported code owner config is not relevant with import mode GLOBAL_CODE_OWNER_SETS_ONLY
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isFalse();
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1077,51 +1212,69 @@
throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create config with global code owner that is imported by the imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user2.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(importMode, keyOfImportedCodeOwnerConfig2);
+
+ // create imported config with global code owner and import
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(codeOwnerConfigReference2)
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(importMode, keyOfImportedCodeOwnerConfig1);
+
// create importing config with global code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(CodeOwnerConfigReference.create(importMode, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config with global code owner and import
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(importMode, "/baz/OWNERS"))
- .create();
-
- // create config with global code owner that is imported by the imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/baz/")
- .addCodeOwnerEmail(user2.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing code owner config, the imported code
// owner config and the code owner config that is imported by the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email(), user2.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1130,47 +1283,54 @@
throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create config with per file code owner that is imported by the imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("foo.md")
+ .addCodeOwnerEmail(user2.email())
+ .build())
+ .create();
+
+ // create imported config with global code owner and import with import mode ALL
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig2))
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config with global code owner and import with import mode
// GLOBAL_CODE_OWNER_SETS_ONLY
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config with global code owner and import with import mode ALL
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/baz/OWNERS"))
- .create();
-
- // create config with per file code owner that is imported by the imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/baz/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("foo.md")
- .addCodeOwnerEmail(user2.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -1178,11 +1338,23 @@
// Expectation: we get the global owners from the importing code owner config and the imported
// code owner config but not the per file code owner from the code owner config that is imported
// by the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ keyOfImportedCodeOwnerConfig2)));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1197,6 +1369,21 @@
private void testImportCodeOwnerConfigWithNameExtension(String nameOfImportedCodeOwnerConfig)
throws Exception {
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .fileName(nameOfImportedCodeOwnerConfig)
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -1205,22 +1392,9 @@
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
- "/bar/" + nameOfImportedCodeOwnerConfig))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .fileName(nameOfImportedCodeOwnerConfig)
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -1231,47 +1405,74 @@
// Expectation: we get the global code owners from the importing and the imported code owner
// config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void cyclicImports() throws Exception {
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/OWNERS");
+
+ // create imported config with global code owner and that imports the importing config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(codeOwnerConfigReference2)
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
+ .fileName("OWNERS")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config with global code owner and that imports the importing config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/OWNERS"))
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig,
+ importingCodeOwnerConfigKey,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1327,72 +1528,91 @@
@Test
public void importWithRelativePath() throws Exception {
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import with relative path
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/bar/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "../baz/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/foo/baz/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void importFromNonExistingProjectIsIgnored() throws Exception {
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(Project.nameKey("non-existing"), "master", "/bar/", "OWNERS");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import from non-existing project
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(Project.nameKey("non-existing"))
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format("project %s not found", keyOfImportedCodeOwnerConfig.project())));
}
@Test
@@ -1402,32 +1622,33 @@
ConfigInput configInput = new ConfigInput();
configInput.state = ProjectState.HIDDEN;
gApi.projects().name(hiddenProject.get()).config(configInput);
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(hiddenProject)
- .branch("master")
- .folderPath("/")
- .addCodeOwnerEmail(user.email())
- .create();
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(hiddenProject)
+ .branch("master")
+ .folderPath("/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
// create importing config with global code owner and import from the hidden project
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/OWNERS")
- .setProject(hiddenProject)
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
@@ -1435,85 +1656,111 @@
// Expectation: we get the global owners from the importing code owner config, the global code
// owners from the imported code owner config are ignored since the project that contains the
// code owner config is hidden
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format(
+ "state of project %s doesn't permit read",
+ keyOfImportedCodeOwnerConfig.project())));
}
@Test
public void importFromNonExistingBranchIsIgnored() throws Exception {
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(project, "non-existing", "/bar/", "OWNERS");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import from non-existing branch
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(project)
- .setBranch("non-existing")
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing code owner config
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ "code owner config does not exist (revision = current)"));
}
@Test
public void importFromOtherProject() throws Exception {
Project.NameKey otherProject = projectOperations.newProject().create();
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import with relative path
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(otherProject)
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1527,43 +1774,52 @@
// Create other branches in other project.
createBranch(BranchNameKey.create(otherProject, branchName));
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch(branchName)
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
+ .setProject(otherProject)
+ .build();
+
// create importing config with global code owner and import with relative path
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch(branchName)
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(otherProject)
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch(branchName)
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead(branchName),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1572,44 +1828,50 @@
String otherBranch = "foo";
createBranch(BranchNameKey.create(project, otherBranch));
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch(otherBranch)
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import with relative path
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(project)
- .setBranch(otherBranch)
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch(otherBranch)
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -1620,48 +1882,60 @@
String otherBranch = "foo";
createBranch(BranchNameKey.create(otherProject, otherBranch));
+ // create imported config with global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch(otherBranch)
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// create importing config with global code owner and import with relative path
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS")
- .setProject(otherProject)
- .setBranch(otherBranch)
- .build())
+ .addImport(codeOwnerConfigReference)
.create();
- // create imported config with global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch(otherBranch)
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing and the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void nonResolveablePerFileImportIsIgnored() throws Exception {
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(project, "master", "/non-existing/", "OWNERS");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config with non-resolveable per file import
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -1673,10 +1947,7 @@
CodeOwnerSet.builder()
.addCodeOwnerEmail(admin.email())
.addPathExpression("foo.md")
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
- "/non-existing/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.build())
.create();
@@ -1690,16 +1961,50 @@
// Expectation: we get the per file code owner from the importing code owner config, the
// non-resolveable per file import is silently ignored
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports()).isTrue();
+ assertThat(pathCodeOwnersResult.resolvedImports()).isEmpty();
+ assertThat(pathCodeOwnersResult.unresolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ String.format(
+ "code owner config does not exist (revision = %s)",
+ projectOperations
+ .project(keyOfImportedCodeOwnerConfig.project())
+ .getHead(keyOfImportedCodeOwnerConfig.branchNameKey().branch())
+ .name())));
}
@Test
public void perFileImport() throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create imported config with ignoreParentCodeOwners = true, a global code owner and a per file
+ // code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .ignoreParentCodeOwners()
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("foo.md")
+ .addCodeOwnerEmail(user2.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config with per code owner and per file import
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -1711,28 +2016,10 @@
CodeOwnerSet.builder()
.addPathExpression("foo.md")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.build())
.create();
- // create imported config with ignoreParentCodeOwners = true, a global code owner and a per file
- // code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .ignoreParentCodeOwners()
- .addCodeOwnerEmail(user.email())
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("foo.md")
- .addCodeOwnerEmail(user2.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -1744,23 +2031,58 @@
// Expectation: we get the per file code owners from the importing and the global code owner
// from the imported code owner config, but not the per file code owner from the imported code
// owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
// Expectation: the ignoreParentCodeOwners flag from the imported code owner config is ignored
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners())
- .isFalse();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.ignoreParentCodeOwners()).isFalse();
+
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void importsOfPerFileImportedCodeOwnerConfigAreResolved() throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create config with global code owner that is imported by the imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user2.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig2);
+
+ // create imported config with global code owner and global import
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(codeOwnerConfigReference2)
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config with per file code owner and per file import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
@@ -1770,56 +2092,75 @@
CodeOwnerSet.builder()
.addPathExpression("foo.md")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.build())
.create();
- // create imported config with global code owner and global import
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/baz/OWNERS"))
- .create();
-
- // create config with global code owner that is imported by the imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/baz/")
- .addCodeOwnerEmail(user2.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
// Expectation: we get the global owners from the importing code owner config, the imported code
// owner config and the code owner config that is imported by the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email(), user2.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void onlyGlobalCodeOwnersAreImportedForTransitivePerFileImports() throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create config with per file code owner that is imported by the imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("foo.md")
+ .addCodeOwnerEmail(user2.email())
+ .build())
+ .create();
+
+ // create imported config with per global owner and global import with mode ALL
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig2))
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config with per file code owner and per file import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
@@ -1829,39 +2170,14 @@
CodeOwnerSet.builder()
.addPathExpression("foo.md")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.build())
.create();
- // create imported config with per global owner and global import with mode ALL
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/baz/OWNERS"))
- .create();
-
- // create config with per file code owner that is imported by the imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/baz/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("foo.md")
- .addCodeOwnerEmail(user2.email())
- .build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
@@ -1869,104 +2185,156 @@
// Expectation: we get the global owners from the importing code owner config and the imported
// code owner config, but not the per file code owner from the code owner config that is
// imported by the imported code owner config
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ keyOfImportedCodeOwnerConfig2)));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
public void onlyMatchingTransitivePerFileImportsAreImported() throws Exception {
TestAccount user2 = accountCreator.user2();
+ // create config with global code owner that is imported by the imported config for *.md files
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/md/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig2);
+
+ // create config with global code owner that is imported by the imported config for *.txt files
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig3 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/txt/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user2.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference3 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig3);
+
+ // create imported config with 2 per file imports, one for *.md files and one for *.txt
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("*.md")
+ .addImport(codeOwnerConfigReference2)
+ .build())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("*.txt")
+ .addImport(codeOwnerConfigReference3)
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config with global import
- CodeOwnerConfig.Key rootCodeOwnerConfigKey =
+ CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config with 2 per file imports, one for *.md files and one for *.txt
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("*.md")
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/md/OWNERS"))
- .build())
- .addCodeOwnerSet(
- CodeOwnerSet.builder()
- .addPathExpression("*.txt")
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/txt/OWNERS"))
- .build())
- .create();
-
- // create config with global code owner that is imported by the imported config for *.md files
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/md/")
- .addCodeOwnerEmail(user.email())
- .create();
-
- // create config with global code owner that is imported by the imported config for *.txt files
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/txt/")
- .addCodeOwnerEmail(user2.email())
- .create();
-
// Expectation for foo.xyz file: code owners is empty since foo.xyz neither matches *.md nor
// *.txt
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.xyz"));
assertThat(pathCodeOwners).isPresent();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners()).isEmpty();
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners()).isEmpty();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
// Expectation for foo.md file: code owners contains only user since foo.md only matches *.md
pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.md"));
assertThat(pathCodeOwners).isPresent();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(user.email());
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
// Expectation for foo.txt file: code owners contains only user2 since foo.txt only matches
// *.txt
pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
- rootCodeOwnerConfigKey,
+ importingCodeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
Paths.get("/foo.txt"));
assertThat(pathCodeOwners).isPresent();
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(user2.email());
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig3,
+ codeOwnerConfigReference3));
}
@Test
@@ -2090,6 +2458,20 @@
@Test
public void perFileRuleThatIgnoresGlobalCodeOwnersCanImportGlobalCodeOwnersFromOtherFile()
throws Exception {
+ // create imported config with a global code owner
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
// create importing config that:
// * has a global code owner
// * has a per-file import for md files
@@ -2105,21 +2487,10 @@
CodeOwnerSet.builder()
.addPathExpression(testPathExpressions.matchFileType("md"))
.setIgnoreGlobalAndParentCodeOwners()
- .addImport(
- CodeOwnerConfigReference.create(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.build())
.create();
- // create imported config with a global code owner
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(project)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -2131,11 +2502,17 @@
// Expectation: we get the global code owner from the imported code owner config (since it is
// imported by a matching per-file rule), the global code owner from the importing code owner
// config is ignored (since the matching per-file rule ignores parent and global code owners)
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(user.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -2146,6 +2523,24 @@
TestAccount user3 =
accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
+ // create imported config that has a matching per-file rule
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user2.email())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("md"))
+ .addCodeOwnerEmail(user3.email())
+ .build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, keyOfImportedCodeOwnerConfig);
+
// 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 =
@@ -2155,8 +2550,7 @@
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"))
+ .addImport(codeOwnerConfigReference)
.addCodeOwnerSet(
CodeOwnerSet.builder()
.addPathExpression(testPathExpressions.matchFileType("md"))
@@ -2165,20 +2559,6 @@
.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(),
@@ -2191,11 +2571,17 @@
// 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())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(user.email(), user3.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -2204,6 +2590,35 @@
Project.NameKey otherProject = projectOperations.newProject().create();
+ // create transitively imported config in other project
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch("master")
+ .folderPath("/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(user2.email()).build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig2);
+
+ // create imported config in other project
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(codeOwnerConfigReference2)
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -2212,32 +2627,9 @@
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/bar/OWNERS")
- .setProject(otherProject)
- .build())
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config in other project
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/baz/OWNERS"))
- .create();
-
- // create transitively imported config in other project
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch("master")
- .folderPath("/baz/")
- .addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(user2.email()).build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -2248,11 +2640,21 @@
// Expectation: we get the global owners from the importing code owner config and from the
// directly and transitively imported code owner configs in the other project
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
+ assertThat(pathCodeOwnersResult.getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email(), user2.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
@Test
@@ -2261,6 +2663,35 @@
Project.NameKey otherProject = projectOperations.newProject().create();
+ // create transitively imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch("master")
+ .folderPath("/bar/baz/")
+ .fileName("OWNERS")
+ .addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(user2.email()).build())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig2);
+
+ // create imported config
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(otherProject)
+ .branch("master")
+ .folderPath("/bar/")
+ .fileName("OWNERS")
+ .addCodeOwnerEmail(user.email())
+ .addImport(codeOwnerConfigReference2)
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig1);
+
// create importing config
CodeOwnerConfig.Key importingCodeOwnerConfigKey =
codeOwnerConfigOperations
@@ -2269,32 +2700,9 @@
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
- .addImport(
- CodeOwnerConfigReference.builder(
- CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "bar/OWNERS")
- .setProject(otherProject)
- .build())
+ .addImport(codeOwnerConfigReference1)
.create();
- // create imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch("master")
- .folderPath("/bar/")
- .addCodeOwnerEmail(user.email())
- .addImport(CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "baz/OWNERS"))
- .create();
-
- // create transitively imported config
- codeOwnerConfigOperations
- .newCodeOwnerConfig()
- .project(otherProject)
- .branch("master")
- .folderPath("/bar/baz/")
- .addCodeOwnerSet(CodeOwnerSet.builder().addCodeOwnerEmail(user2.email()).build())
- .create();
-
Optional<PathCodeOwners> pathCodeOwners =
pathCodeOwnersFactory.create(
transientCodeOwnerConfigCacheProvider.get(),
@@ -2305,11 +2713,21 @@
// Expectation: we get the global owners from the importing code owner config and from the
// directly and transitively imported code owner configs
+ PathCodeOwnersResult pathCodeOwnersResult = pathCodeOwners.get().resolveCodeOwnerConfig().get();
assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().getPathCodeOwners())
.comparingElementsUsing(hasEmail())
.containsExactly(admin.email(), user.email(), user2.email());
- assertThat(pathCodeOwners.get().resolveCodeOwnerConfig().get().hasUnresolvedImports())
- .isFalse();
+ assertThat(pathCodeOwnersResult.resolvedImports())
+ .containsExactly(
+ CodeOwnerConfigImport.createResolvedImport(
+ importingCodeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig1,
+ codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig1,
+ keyOfImportedCodeOwnerConfig2,
+ codeOwnerConfigReference2));
+ assertThat(pathCodeOwnersResult.unresolvedImports()).isEmpty();
}
private CodeOwnerConfig.Builder createCodeOwnerBuilder() {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java
deleted file mode 100644
index ce0494f..0000000
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.plugins.codeowners.backend;
-
-import org.junit.Test;
-
-/** Tests for {@link UnresolvedImport}. */
-public class UnresolvedImportTest extends AbstractAutoValueTest {
- @Test
- public void toStringIncludesAllData() throws Exception {
- UnresolvedImport unresolvedImport =
- UnresolvedImport.create(
- CodeOwnerConfig.Key.create(project, "master", "/"),
- CodeOwnerConfig.Key.create(project, "master", "/bar/"),
- CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"),
- "test message");
- assertThatToStringIncludesAllData(unresolvedImport, UnresolvedImport.class);
- }
-}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJsonIT.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJsonIT.java
new file mode 100644
index 0000000..ed0b301
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerConfigFileJsonIT.java
@@ -0,0 +1,388 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.restapi;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImport;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests for {@link CodeOwnerConfigFileJson}. */
+public class CodeOwnerConfigFileJsonIT extends AbstractCodeOwnersIT {
+ private CodeOwnerConfigFileJson CodeOwnerConfigFileJson;
+
+ @Before
+ public void setUpCodeOwnersPlugin() throws Exception {
+ CodeOwnerConfigFileJson = plugin.getSysInjector().getInstance(CodeOwnerConfigFileJson.class);
+ }
+
+ @Test
+ public void cannotFormatWithNullCodeOwnerConfigKey() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ CodeOwnerConfigFileJson.format(
+ /* codeOwnerConfigKey= */ null,
+ /* resolvedImports= */ ImmutableList.of(),
+ /* unresolvedImports= */ ImmutableList.of()));
+ assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigKey");
+ }
+
+ @Test
+ public void cannotFormatWithNullResolvedImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ CodeOwnerConfig.Key.create(Project.nameKey("project"), "master", "/");
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ null,
+ /* unresolvedImports= */ ImmutableList.of()));
+ assertThat(npe).hasMessageThat().isEqualTo("resolvedImports");
+ }
+
+ @Test
+ public void cannotFormatWithNullUnresolvedImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ CodeOwnerConfig.Key.create(Project.nameKey("project"), "master", "/");
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ ImmutableList.of(),
+ /* unresolvedImports= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("unresolvedImports");
+ }
+
+ @Test
+ public void formatWithoutImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/baz/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ ImmutableList.of(),
+ /* unresolvedImports= */ ImmutableList.of());
+ assertThat(codeOwnerConfigFileInfo.project).isEqualTo(codeOwnerConfigKey.project().get());
+ assertThat(codeOwnerConfigFileInfo.branch)
+ .isEqualTo(codeOwnerConfigKey.branchNameKey().branch());
+ assertThat(codeOwnerConfigFileInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath());
+ assertThat(codeOwnerConfigFileInfo.importMode).isNull();
+ assertThat(codeOwnerConfigFileInfo.imports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedImports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedErrorMessage).isNull();
+ }
+
+ @Test
+ public void formatWithUnresolvedImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(project, "stable", "/foo/baz/");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ ImmutableList.of(),
+ /* unresolvedImports= */ ImmutableList.of(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ codeOwnerConfigKey,
+ keyOfImportedCodeOwnerConfig,
+ codeOwnerConfigReference,
+ "error message")));
+ assertThat(codeOwnerConfigFileInfo.project).isEqualTo(codeOwnerConfigKey.project().get());
+ assertThat(codeOwnerConfigFileInfo.branch)
+ .isEqualTo(codeOwnerConfigKey.branchNameKey().branch());
+ assertThat(codeOwnerConfigFileInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath());
+ assertThat(codeOwnerConfigFileInfo.importMode).isNull();
+ assertThat(codeOwnerConfigFileInfo.imports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(codeOwnerConfigFileInfo.unresolvedImports).hasSize(1);
+ CodeOwnerConfigFileInfo unresolvedImportInfo =
+ Iterables.getOnlyElement(codeOwnerConfigFileInfo.unresolvedImports);
+ assertThat(unresolvedImportInfo.project)
+ .isEqualTo(keyOfImportedCodeOwnerConfig.project().get());
+ assertThat(unresolvedImportInfo.branch)
+ .isEqualTo(keyOfImportedCodeOwnerConfig.branchNameKey().branch());
+ assertThat(unresolvedImportInfo.path)
+ .isEqualTo(
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfImportedCodeOwnerConfig).getFilePath());
+ assertThat(unresolvedImportInfo.importMode).isEqualTo(codeOwnerConfigReference.importMode());
+ assertThat(unresolvedImportInfo.imports).isNull();
+ assertThat(unresolvedImportInfo.unresolvedImports).isNull();
+ assertThat(unresolvedImportInfo.unresolvedErrorMessage).isEqualTo("error message");
+ }
+
+ @Test
+ public void formatWithImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ CodeOwnerConfig.Key.create(project, "stable", "/foo/baz/");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ ImmutableList.of(
+ CodeOwnerConfigImport.createResolvedImport(
+ codeOwnerConfigKey, keyOfImportedCodeOwnerConfig, codeOwnerConfigReference)),
+ /* unresolvedImports= */ ImmutableList.of());
+ assertThat(codeOwnerConfigFileInfo.project).isEqualTo(codeOwnerConfigKey.project().get());
+ assertThat(codeOwnerConfigFileInfo.branch)
+ .isEqualTo(codeOwnerConfigKey.branchNameKey().branch());
+ assertThat(codeOwnerConfigFileInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath());
+ assertThat(codeOwnerConfigFileInfo.importMode).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedImports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(codeOwnerConfigFileInfo.imports).hasSize(1);
+ CodeOwnerConfigFileInfo resolvedImportInfo =
+ Iterables.getOnlyElement(codeOwnerConfigFileInfo.imports);
+ assertThat(resolvedImportInfo.project).isEqualTo(keyOfImportedCodeOwnerConfig.project().get());
+ assertThat(resolvedImportInfo.branch)
+ .isEqualTo(keyOfImportedCodeOwnerConfig.branchNameKey().branch());
+ assertThat(resolvedImportInfo.path)
+ .isEqualTo(
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfImportedCodeOwnerConfig).getFilePath());
+ assertThat(resolvedImportInfo.importMode).isEqualTo(codeOwnerConfigReference.importMode());
+ assertThat(resolvedImportInfo.imports).isNull();
+ assertThat(resolvedImportInfo.unresolvedImports).isNull();
+ assertThat(resolvedImportInfo.unresolvedErrorMessage).isNull();
+ }
+
+ @Test
+ public void formatWithNestedImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/baz")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, keyOfImportedCodeOwnerConfig);
+
+ CodeOwnerConfig.Key keyOfNestedImportedCodeOwnerConfig1 =
+ CodeOwnerConfig.Key.create(project, "stable", "/foo/baz1/");
+ CodeOwnerConfigReference nestedCodeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ keyOfNestedImportedCodeOwnerConfig1);
+
+ CodeOwnerConfig.Key keyOfNestedImportedCodeOwnerConfig2 =
+ CodeOwnerConfig.Key.create(project, "stable", "/foo/baz2/");
+ CodeOwnerConfigReference nestedCodeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(
+ CodeOwnerConfigImportMode.ALL, keyOfNestedImportedCodeOwnerConfig2);
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey,
+ /* resolvedImports= */ ImmutableList.of(
+ CodeOwnerConfigImport.createResolvedImport(
+ codeOwnerConfigKey, keyOfImportedCodeOwnerConfig, codeOwnerConfigReference),
+ CodeOwnerConfigImport.createResolvedImport(
+ keyOfImportedCodeOwnerConfig,
+ keyOfNestedImportedCodeOwnerConfig1,
+ nestedCodeOwnerConfigReference1)),
+ /* unresolvedImports= */ ImmutableList.of(
+ CodeOwnerConfigImport.createUnresolvedImport(
+ keyOfImportedCodeOwnerConfig,
+ keyOfNestedImportedCodeOwnerConfig2,
+ nestedCodeOwnerConfigReference2,
+ "error message")));
+ assertThat(codeOwnerConfigFileInfo.project).isEqualTo(codeOwnerConfigKey.project().get());
+ assertThat(codeOwnerConfigFileInfo.branch)
+ .isEqualTo(codeOwnerConfigKey.branchNameKey().branch());
+ assertThat(codeOwnerConfigFileInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath());
+ assertThat(codeOwnerConfigFileInfo.importMode).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedImports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(codeOwnerConfigFileInfo.imports).hasSize(1);
+ CodeOwnerConfigFileInfo resolvedImportInfo =
+ Iterables.getOnlyElement(codeOwnerConfigFileInfo.imports);
+ assertThat(resolvedImportInfo.project).isEqualTo(keyOfImportedCodeOwnerConfig.project().get());
+ assertThat(resolvedImportInfo.branch)
+ .isEqualTo(keyOfImportedCodeOwnerConfig.branchNameKey().branch());
+ assertThat(resolvedImportInfo.path)
+ .isEqualTo(
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfImportedCodeOwnerConfig).getFilePath());
+ assertThat(resolvedImportInfo.importMode).isEqualTo(codeOwnerConfigReference.importMode());
+ assertThat(resolvedImportInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(resolvedImportInfo.imports).hasSize(1);
+ CodeOwnerConfigFileInfo nestedResolvedImportInfo =
+ Iterables.getOnlyElement(resolvedImportInfo.imports);
+ assertThat(nestedResolvedImportInfo.project)
+ .isEqualTo(keyOfNestedImportedCodeOwnerConfig1.project().get());
+ assertThat(nestedResolvedImportInfo.branch)
+ .isEqualTo(keyOfNestedImportedCodeOwnerConfig1.branchNameKey().branch());
+ assertThat(nestedResolvedImportInfo.path)
+ .isEqualTo(
+ codeOwnerConfigOperations
+ .codeOwnerConfig(keyOfNestedImportedCodeOwnerConfig1)
+ .getFilePath());
+ assertThat(nestedResolvedImportInfo.importMode)
+ .isEqualTo(nestedCodeOwnerConfigReference1.importMode());
+ assertThat(nestedResolvedImportInfo.imports).isNull();
+ assertThat(nestedResolvedImportInfo.unresolvedImports).isNull();
+ assertThat(nestedResolvedImportInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(resolvedImportInfo.unresolvedImports).hasSize(1);
+ CodeOwnerConfigFileInfo nestedUnresolvedImportInfo1 =
+ Iterables.getOnlyElement(resolvedImportInfo.unresolvedImports);
+ assertThat(nestedUnresolvedImportInfo1.project)
+ .isEqualTo(keyOfNestedImportedCodeOwnerConfig2.project().get());
+ assertThat(nestedUnresolvedImportInfo1.branch)
+ .isEqualTo(keyOfNestedImportedCodeOwnerConfig2.branchNameKey().branch());
+ assertThat(nestedUnresolvedImportInfo1.path)
+ .isEqualTo(
+ codeOwnerConfigOperations
+ .codeOwnerConfig(keyOfNestedImportedCodeOwnerConfig2)
+ .getFilePath());
+ assertThat(nestedUnresolvedImportInfo1.importMode)
+ .isEqualTo(nestedCodeOwnerConfigReference2.importMode());
+ assertThat(nestedUnresolvedImportInfo1.imports).isNull();
+ assertThat(nestedUnresolvedImportInfo1.unresolvedImports).isNull();
+ assertThat(nestedUnresolvedImportInfo1.unresolvedErrorMessage).isEqualTo("error message");
+ }
+
+ @Test
+ public void formatWithCyclicImports() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference1 =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, codeOwnerConfigKey1);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/baz")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+ CodeOwnerConfigReference codeOwnerConfigReference2 =
+ createCodeOwnerConfigReference(CodeOwnerConfigImportMode.ALL, codeOwnerConfigKey2);
+
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ CodeOwnerConfigFileJson.format(
+ codeOwnerConfigKey1,
+ /* resolvedImports= */ ImmutableList.of(
+ CodeOwnerConfigImport.createResolvedImport(
+ codeOwnerConfigKey1, codeOwnerConfigKey2, codeOwnerConfigReference1),
+ CodeOwnerConfigImport.createResolvedImport(
+ codeOwnerConfigKey2, codeOwnerConfigKey1, codeOwnerConfigReference2)),
+ /* unresolvedImports= */ ImmutableList.of());
+
+ assertThat(codeOwnerConfigFileInfo.project).isEqualTo(codeOwnerConfigKey1.project().get());
+ assertThat(codeOwnerConfigFileInfo.branch)
+ .isEqualTo(codeOwnerConfigKey1.branchNameKey().branch());
+ assertThat(codeOwnerConfigFileInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath());
+ assertThat(codeOwnerConfigFileInfo.importMode).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedImports).isNull();
+ assertThat(codeOwnerConfigFileInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(codeOwnerConfigFileInfo.imports).hasSize(1);
+ CodeOwnerConfigFileInfo resolvedImportInfo =
+ Iterables.getOnlyElement(codeOwnerConfigFileInfo.imports);
+ assertThat(resolvedImportInfo.project).isEqualTo(codeOwnerConfigKey2.project().get());
+ assertThat(resolvedImportInfo.branch).isEqualTo(codeOwnerConfigKey2.branchNameKey().branch());
+ assertThat(resolvedImportInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath());
+ assertThat(resolvedImportInfo.importMode).isEqualTo(codeOwnerConfigReference1.importMode());
+ assertThat(resolvedImportInfo.unresolvedErrorMessage).isNull();
+
+ assertThat(resolvedImportInfo.imports).hasSize(1);
+ CodeOwnerConfigFileInfo nestedResolvedImportInfo =
+ Iterables.getOnlyElement(resolvedImportInfo.imports);
+ assertThat(nestedResolvedImportInfo.project).isEqualTo(codeOwnerConfigKey1.project().get());
+ assertThat(nestedResolvedImportInfo.branch)
+ .isEqualTo(codeOwnerConfigKey1.branchNameKey().branch());
+ assertThat(nestedResolvedImportInfo.path)
+ .isEqualTo(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath());
+ assertThat(nestedResolvedImportInfo.importMode)
+ .isEqualTo(codeOwnerConfigReference1.importMode());
+ assertThat(nestedResolvedImportInfo.imports).isNull();
+ assertThat(nestedResolvedImportInfo.unresolvedImports).isNull();
+ assertThat(nestedResolvedImportInfo.unresolvedErrorMessage).isNull();
+ }
+}
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 04e8ffd..c97ba2c 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -499,7 +499,42 @@
{
"account": {
"_account_id": 1001439
- },
+ }
+ },
+ {
+ "account": {
+ "_account_id": 1007265
+ }
+ },
+ {
+ "account": {
+ "_account_id": 1009877
+ }
+ },
+ {
+ "account": {
+ "_account_id": 1002930
+ }
+ }
+ ],
+ "code_owner_configs": [
+ {
+ "project": "foo/bar",
+ "branch": "master",
+ "path": "/docs/OWNERS"
+ },
+ {
+ "project": "foo/bar",
+ "branch": "master",
+ "path": "/OWNERS",
+ "imports": [
+ {
+ "project": "foo",
+ "branch": "master",
+ "path": "/OWNERS",
+ "import_mode": "ALL",
+ }
+ ]
}
]
}
@@ -918,6 +953,22 @@
---
+### <a id="code-owner-config-file-info"> CodeOwnerConfigFileInfo
+The `CodeOwnerConfigFileInfo` entity contains information about a code owner
+config file and its imports.
+
+| Field Name | | Description |
+| ---------- | -------- | ----------- |
+| `project` || The name of the project from which the code owner config was loaded, or for unresolved imports, from which the code owner config was supposed to be loaded.
+| `branch` || The name of the branch from which the code owner config was loaded, or for unresolved imports, from which the code owner config was supposed to be loaded.
+| `path` || The absolute path of the code owner config file.
+| `imports` | optional | Imported code owner config files as [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entities.
+| `unresolved_imports` | optional | Imported code owner config files that couldn't be resolved as [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entities.
+| `unresolved_error_message` | optional | Message explaining why this code owner config couldn't be resolved. Only set if the `CodeOwnerConfigFileInfo` represents an import code owner config file that couldn't be resolved.
+| `import_mode` | optional | The import mode (`ALL` or `GLOBAL_CODE_OWNER_SETS_ONLY`). Only set if the `CodeOwnerConfigFileInfo` represents an imported code owner config file.
+
+---
+
### <a id="code-owner-config-info"> CodeOwnerConfigInfo
The `CodeOwnerConfigInfo` entity contains information about a code owner config
for a path.
@@ -1042,6 +1093,7 @@
| ------------- | -------- | ----------- |
| `code_owners` | | List of code owners as [CodeOwnerInfo](#code-owner-info) entities. The code owners are sorted by a score that is computed from mutliple [scoring factors](#scoringFactors).
| `owned_by_all_users` | optional | Whether the path is owned by all users. Not set if `false`.
+| `code_owner_configs` || The code owner config files that have been inspected to gather the code owners as [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entities.
| `debug_logs` | optional | Debug logs that may help to understand why a user is or isn't suggested as a code owner. Only set if requested via `--debug`. This information is purely for debugging and the output may be changed at any time. This means bot callers must not parse the debug logs.
### <a id="file-code-owner-status-info"> FileCodeOwnerStatusInfo