Merge changes Ib28802d3,If54a64ad,I833e9c2b,I0ecdda81,Ib654accc
* changes:
Distinguish btw. messages that can be shown to all users vs. admins only
CodeOwnerResolverTest: Drop msg that is optionally expected but never occurs
CheckCodeOwner: Return whether parent/folder owners are ignored
CheckedCodeOwnerConfigFileInfoSubject: Drop 'assert' method prefix
CheckCodeOwner: Replace 'code_owner_config_file_paths' field
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckedCodeOwnerConfigFileInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CheckedCodeOwnerConfigFileInfo.java
new file mode 100644
index 0000000..e651865
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckedCodeOwnerConfigFileInfo.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 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;
+
+/**
+ * Representation of a code owner config file with check results in the REST API.
+ *
+ * <p>This class determines the JSON format of code owner config files with check results in the
+ * REST API.
+ */
+public class CheckedCodeOwnerConfigFileInfo {
+ /** The code owner config file that was checked. */
+ public CodeOwnerConfigFileInfo codeOwnerConfigFileInfo;
+
+ /**
+ * Whether this code owner config file assigns code ownership to the specified email and path.
+ *
+ * <p>Note that if code ownership is assigned to the email via a code owner config file, but the
+ * email is not resolvable (see the {@link CodeOwnerCheckInfo#isResolvable} field), the user is
+ * not a code owner.
+ */
+ public boolean assignsCodeOwnershipToUser;
+
+ /** Whether code owners from parent directory are ignored. */
+ public boolean areParentCodeOwnersIgnored;
+
+ /**
+ * Whether folder code owners are ignored (i.e. whether there is a matching per-file rule that
+ * ignores folder code owners, e.g. by using "set noparent").
+ */
+ public boolean areFolderCodeOwnersIgnored;
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java
index 15c8558..55172a6 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java
@@ -30,11 +30,11 @@
*
* <ul>
* <li>the given email is resolvable (see {@link #isResolvable}) and
- * <li>any code owner config file assigns codeownership to the email for the path (see {@link
- * #codeOwnerConfigFilePaths}) or the email is configured as default code owner (see {@link
- * CodeOwnerCheckInfo#isDefaultCodeOwner} field) or the email is configured as global code
- * owner (see {@link #isGlobalCodeOwner} field) or the user is a fallback code owner (see
- * {@link #isFallbackCodeOwner} field)
+ * <li>any code owner config file assigns code ownership to the email for the path (see {@link
+ * CheckedCodeOwnerConfigFileInfo#assignsCodeOwnershipToUser} field of the inspected {@link
+ * #checkedCodeOwnerConfigs}) or the email is configured as global code owner (see {@link
+ * #isGlobalCodeOwner} field) or the user is a fallback code owner (see {@link
+ * #isFallbackCodeOwner} field)
* </ul>
*/
public boolean isCodeOwner;
@@ -49,7 +49,7 @@
* The code owner config files that are relevant for computing the code ownership, i.e. all code
* owner config files which have been inspected to compute the code ownership.
*/
- public List<CodeOwnerConfigFileInfo> codeOwnerConfigs;
+ public List<CheckedCodeOwnerConfigFileInfo> checkedCodeOwnerConfigs;
/**
* Whether the user to which the given email was resolved has read permissions on the branch.
@@ -97,15 +97,6 @@
public Boolean canApproveChange;
/**
- * Paths of the code owner config files that assign code ownership to the given email for the
- * specified path.
- *
- * <p>If code ownership is assigned to the email via a code owner config files, but the email is
- * not resolvable (see {@link #isResolvable} field), the user is not a code owner.
- */
- public List<String> codeOwnerConfigFilePaths;
-
- /**
* Whether the given email is a fallback code owner of the specified path in the branch.
*
* <p>True if:
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
index f6718eb..2f55c21 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
@@ -281,18 +281,19 @@
ImmutableMultimap<CodeOwnerReference, CodeOwnerAnnotation> annotationsByCodeOwnerReference,
ImmutableList<CodeOwnerConfigImport> resolvedImports,
ImmutableList<CodeOwnerConfigImport> unresolvedImports,
- ImmutableList<String> pathCodeOwnersMessages) {
+ ImmutableList<DebugMessage> pathCodeOwnersMessages) {
requireNonNull(codeOwnerReferences, "codeOwnerReferences");
requireNonNull(resolvedImports, "resolvedImports");
requireNonNull(unresolvedImports, "unresolvedImports");
requireNonNull(pathCodeOwnersMessages, "pathCodeOwnersMessages");
try (Timer0.Context ctx = codeOwnerMetrics.resolveCodeOwnerReferences.start()) {
- ImmutableList.Builder<String> messageBuilder = ImmutableList.builder();
+ ImmutableList.Builder<DebugMessage> messageBuilder = ImmutableList.builder();
messageBuilder.addAll(pathCodeOwnersMessages);
unresolvedImports.forEach(
unresolvedImport ->
- messageBuilder.add(unresolvedImportFormatter.format(unresolvedImport)));
+ messageBuilder.add(
+ DebugMessage.createMessage(unresolvedImportFormatter.format(unresolvedImport))));
AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
AtomicBoolean hasUnresolvedCodeOwners = new AtomicBoolean(false);
@@ -356,12 +357,13 @@
if (CodeOwnerResolver.ALL_USERS_WILDCARD.equals(codeOwnerReference.email())) {
return OptionalResultWithMessages.createEmpty(
- String.format(
- "cannot resolve code owner email %s: no account with this email exists",
- CodeOwnerResolver.ALL_USERS_WILDCARD));
+ DebugMessage.createMessage(
+ String.format(
+ "cannot resolve code owner email %s: no account with this email exists",
+ CodeOwnerResolver.ALL_USERS_WILDCARD)));
}
- ImmutableList.Builder<String> messageBuilder = ImmutableList.builder();
+ ImmutableList.Builder<DebugMessage> messageBuilder = ImmutableList.builder();
AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
AtomicBoolean hasUnresolvedCodeOwners = new AtomicBoolean(false);
ImmutableMap<CodeOwner, ImmutableSet<CodeOwnerAnnotation>> codeOwnersWithAnnotations =
@@ -371,7 +373,7 @@
hasUnresolvedCodeOwners,
ImmutableSet.of(codeOwnerReference),
/* annotations= */ ImmutableMultimap.of());
- ImmutableList<String> messages = messageBuilder.build();
+ ImmutableList<DebugMessage> messages = messageBuilder.build();
if (codeOwnersWithAnnotations.isEmpty()) {
return OptionalResultWithMessages.createEmpty(messages);
}
@@ -397,7 +399,7 @@
* owners without annotations and Multimap doesn't store keys for which no values are stored)
*/
private ImmutableMap<CodeOwner, ImmutableSet<CodeOwnerAnnotation>> resolve(
- ImmutableList.Builder<String> messages,
+ ImmutableList.Builder<DebugMessage> messages,
AtomicBoolean ownedByAllUsers,
AtomicBoolean hasUnresolvedCodeOwners,
Set<CodeOwnerReference> codeOwnerReferences,
@@ -435,7 +437,7 @@
.filter(filterOutEmailsOfNonVisibleAccounts(messages))
.filter(filterOutNonVisibleSecondaryEmails(messages));
} else {
- messages.add("code owner visibility is not checked");
+ messages.add(DebugMessage.createMessage("code owner visibility is not checked"));
}
ImmutableMap<String, CodeOwner> codeOwnersByEmail =
@@ -501,7 +503,7 @@
* @param messages builder to which debug messages are added
*/
private Predicate<String> filterOutEmailsWithNonAllowedDomains(
- ImmutableList.Builder<String> messages) {
+ ImmutableList.Builder<DebugMessage> messages) {
return email -> {
boolean isEmailDomainAllowed = isEmailDomainAllowed(messages, email);
if (!isEmailDomainAllowed) {
@@ -525,7 +527,7 @@
* contains {@code false}
*/
public OptionalResultWithMessages<Boolean> isEmailDomainAllowed(String email) {
- ImmutableList.Builder<String> messages = ImmutableList.builder();
+ ImmutableList.Builder<DebugMessage> messages = ImmutableList.builder();
boolean isEmailDomainAllowed = isEmailDomainAllowed(messages, email);
return OptionalResultWithMessages.create(isEmailDomainAllowed, messages.build());
}
@@ -541,19 +543,19 @@
* @return {@code true} if the domain of the given email is allowed for code owners, otherwise
* {@code false}
*/
- private boolean isEmailDomainAllowed(ImmutableList.Builder<String> messages, String email) {
+ private boolean isEmailDomainAllowed(ImmutableList.Builder<DebugMessage> messages, String email) {
requireNonNull(messages, "messages");
requireNonNull(email, "email");
ImmutableSet<String> allowedEmailDomains =
codeOwnersPluginConfiguration.getGlobalConfig().getAllowedEmailDomains();
if (allowedEmailDomains.isEmpty()) {
- messages.add("all domains are allowed");
+ messages.add(DebugMessage.createMessage("all domains are allowed"));
return true;
}
if (email.equals(ALL_USERS_WILDCARD)) {
- messages.add("all users wildcard is allowed");
+ messages.add(DebugMessage.createMessage("all users wildcard is allowed"));
return true;
}
@@ -562,13 +564,14 @@
String emailDomain = email.substring(emailAtIndex + 1);
boolean isEmailDomainAllowed = allowedEmailDomains.contains(emailDomain);
messages.add(
- String.format(
- "domain %s of email %s is %s",
- emailDomain, email, isEmailDomainAllowed ? "allowed" : "not allowed"));
+ DebugMessage.createMessage(
+ String.format(
+ "domain %s of email %s is %s",
+ emailDomain, email, isEmailDomainAllowed ? "allowed" : "not allowed")));
return isEmailDomainAllowed;
}
- messages.add(String.format("email %s has no domain", email));
+ messages.add(DebugMessage.createMessage(String.format("email %s has no domain", email)));
return false;
}
@@ -583,7 +586,7 @@
* @return external IDs per email
*/
private ImmutableMap<String, Collection<ExternalId>> lookupExternalIds(
- ImmutableList.Builder<String> messages, ImmutableSet<String> emails) {
+ ImmutableList.Builder<DebugMessage> messages, ImmutableSet<String> emails) {
try {
ImmutableMap<String, Collection<ExternalId>> extIdsByEmail =
externalIdCache.byEmails(emails.toArray(new String[0])).asMap();
@@ -593,9 +596,11 @@
email -> {
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format(
- "cannot resolve code owner email %s: no account with this email exists",
- email));
+ createDebugMessageForNonResolvableEmail(
+ email,
+ String.format(
+ "cannot resolve code owner email %s: no account with this email exists",
+ email)));
});
return extIdsByEmail;
} catch (IOException e) {
@@ -615,7 +620,7 @@
* @return account states per email
*/
private Stream<Pair<String, Collection<AccountState>>> lookupAccounts(
- ImmutableList.Builder<String> messages,
+ ImmutableList.Builder<DebugMessage> messages,
ImmutableMap<String, Collection<ExternalId>> externalIdsByEmail) {
ImmutableSet<Account.Id> accountIds =
externalIdsByEmail.values().stream()
@@ -635,10 +640,12 @@
AccountState accountState = accounts.get(accountId);
if (accountState == null) {
messages.add(
- String.format(
- "cannot resolve account %s for email %s: account does not"
- + " exists",
- accountId, e.getKey()));
+ createDebugMessageForNonResolvableEmail(
+ e.getKey(),
+ String.format(
+ "cannot resolve account %s for email %s: account does not"
+ + " exists",
+ accountId, e.getKey())));
}
return accountState;
})
@@ -656,7 +663,7 @@
* @param messages builder to which debug messages are added
*/
private Function<Pair<String, Collection<AccountState>>, Pair<String, Collection<AccountState>>>
- removeInactiveAccounts(ImmutableList.Builder<String> messages) {
+ removeInactiveAccounts(ImmutableList.Builder<DebugMessage> messages) {
return e -> Pair.of(e.key(), removeInactiveAccounts(messages, e.key(), e.value()));
}
@@ -669,7 +676,7 @@
* @return the account states that belong to active accounts
*/
private ImmutableSet<AccountState> removeInactiveAccounts(
- ImmutableList.Builder<String> messages,
+ ImmutableList.Builder<DebugMessage> messages,
String email,
Collection<AccountState> accountStates) {
return accountStates.stream()
@@ -677,9 +684,10 @@
accountState -> {
if (!accountState.account().isActive()) {
messages.add(
- String.format(
- "ignoring inactive account %s for email %s",
- accountState.account().id(), email));
+ DebugMessage.createMessage(
+ String.format(
+ "ignoring inactive account %s for email %s",
+ accountState.account().id(), email)));
return false;
}
return true;
@@ -696,15 +704,17 @@
* @param messages builder to which debug messages are added
*/
private Predicate<Pair<String, Collection<AccountState>>> filterOutEmailsWithoutAccounts(
- ImmutableList.Builder<String> messages) {
+ ImmutableList.Builder<DebugMessage> messages) {
return e -> {
if (e.value().isEmpty()) {
String email = e.key();
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format(
- "cannot resolve code owner email %s: no active account with this email found",
- email));
+ createDebugMessageForNonResolvableEmail(
+ email,
+ String.format(
+ "cannot resolve code owner email %s: no active account with this email found",
+ email)));
return false;
}
return true;
@@ -721,13 +731,15 @@
* @param messages builder to which debug messages are added
*/
private Predicate<Pair<String, Collection<AccountState>>> filterOutAmbiguousEmails(
- ImmutableList.Builder<String> messages) {
+ ImmutableList.Builder<DebugMessage> messages) {
return e -> {
if (e.value().size() > 1) {
String email = e.key();
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format("cannot resolve code owner email %s: email is ambiguous", email));
+ createDebugMessageForNonResolvableEmail(
+ email,
+ String.format("cannot resolve code owner email %s: email is ambiguous", email)));
return false;
}
return true;
@@ -745,12 +757,14 @@
* @param messages builder to which debug messages are added
*/
private Function<Pair<String, Collection<AccountState>>, Pair<String, AccountState>>
- mapToOnlyAccount(ImmutableList.Builder<String> messages) {
+ mapToOnlyAccount(ImmutableList.Builder<DebugMessage> messages) {
return e -> {
String email = e.key();
AccountState accountState = Iterables.getOnlyElement(e.value());
messages.add(
- String.format("resolved email %s to account %s", email, accountState.account().id()));
+ DebugMessage.createMessage(
+ String.format(
+ "resolved email %s to account %s", email, accountState.account().id())));
return Pair.of(email, accountState);
};
}
@@ -761,18 +775,20 @@
* @param messages builder to which debug messages are added
*/
private Predicate<Pair<String, AccountState>> filterOutEmailsOfNonVisibleAccounts(
- ImmutableList.Builder<String> messages) {
+ ImmutableList.Builder<DebugMessage> messages) {
return e -> {
String email = e.key();
AccountState accountState = e.value();
if (!canSee(accountState)) {
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format(
- "cannot resolve code owner email %s: account %s is not visible to user %s",
+ createDebugMessageForNonResolvableEmail(
email,
- accountState.account().id(),
- user != null ? user.getLoggableName() : currentUser.get().getLoggableName()));
+ String.format(
+ "cannot resolve code owner email %s: account %s is not visible to user %s",
+ email,
+ accountState.account().id(),
+ user != null ? user.getLoggableName() : currentUser.get().getLoggableName())));
return false;
}
@@ -801,27 +817,29 @@
* @param messages builder to which debug messages are added
*/
private Predicate<Pair<String, AccountState>> filterOutNonVisibleSecondaryEmails(
- ImmutableList.Builder<String> messages) {
+ ImmutableList.Builder<DebugMessage> messages) {
return e -> {
String email = e.key();
AccountState accountState = e.value();
if (email.equals(accountState.account().preferredEmail())) {
// the email is a primary email of the account
messages.add(
- String.format(
- "account %s is visible to user %s",
- accountState.account().id(),
- user != null ? user.getLoggableName() : currentUser.get().getLoggableName()));
+ DebugMessage.createMessage(
+ String.format(
+ "account %s is visible to user %s",
+ accountState.account().id(),
+ user != null ? user.getLoggableName() : currentUser.get().getLoggableName())));
return true;
}
if (user != null) {
if (user.hasEmailAddress(email)) {
messages.add(
- String.format(
- "email %s is visible to user %s: email is a secondary email that is owned by this"
- + " user",
- email, user.getLoggableName()));
+ DebugMessage.createAdminOnlyMessage(
+ String.format(
+ "email %s is visible to user %s: email is a secondary email that is owned by this"
+ + " user",
+ email, user.getLoggableName())));
return true;
}
} else if (currentUser.get().isIdentifiedUser()
@@ -829,10 +847,11 @@
// it's a secondary email of the calling user, users can always see their own secondary
// emails
messages.add(
- String.format(
- "email %s is visible to the calling user %s: email is a secondary email that is"
- + " owned by this user",
- email, currentUser.get().getLoggableName()));
+ DebugMessage.createMessage(
+ String.format(
+ "email %s is visible to the calling user %s: email is a secondary email that is"
+ + " owned by this user",
+ email, currentUser.get().getLoggableName())));
return true;
}
@@ -843,32 +862,37 @@
if (!permissionBackend.user(user).test(GlobalPermission.VIEW_SECONDARY_EMAILS)) {
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format(
- "cannot resolve code owner email %s: account %s is referenced by secondary"
- + " email but user %s cannot see secondary emails",
- email, accountState.account().id(), user.getLoggableName()));
+ DebugMessage.createAdminOnlyMessage(
+ String.format(
+ "cannot resolve code owner email %s: account %s is referenced by secondary"
+ + " email but user %s cannot see secondary emails",
+ email, accountState.account().id(), user.getLoggableName())));
return false;
}
messages.add(
- String.format(
- "resolved code owner email %s: account %s is referenced by secondary email"
- + " and user %s can see secondary emails",
- email, accountState.account().id(), user.getLoggableName()));
+ DebugMessage.createAdminOnlyMessage(
+ String.format(
+ "resolved code owner email %s: account %s is referenced by secondary email"
+ + " and user %s can see secondary emails",
+ email, accountState.account().id(), user.getLoggableName())));
return true;
} else if (!permissionBackend.currentUser().test(GlobalPermission.VIEW_SECONDARY_EMAILS)) {
transientCodeOwnerCache.cacheNonResolvable(email);
messages.add(
- String.format(
- "cannot resolve code owner email %s: account %s is referenced by secondary email"
- + " but the calling user %s cannot see secondary emails",
- email, accountState.account().id(), currentUser.get().getLoggableName()));
+ createDebugMessageForNonResolvableEmail(
+ email,
+ String.format(
+ "cannot resolve code owner email %s: account %s is referenced by secondary email"
+ + " but the calling user %s cannot see secondary emails",
+ email, accountState.account().id(), currentUser.get().getLoggableName())));
return false;
} else {
messages.add(
- String.format(
- "resolved code owner email %s: account %s is referenced by secondary email"
- + " and the calling user %s can see secondary emails",
- email, accountState.account().id(), currentUser.get().getLoggableName()));
+ DebugMessage.createMessage(
+ String.format(
+ "resolved code owner email %s: account %s is referenced by secondary email"
+ + " and the calling user %s can see secondary emails",
+ email, accountState.account().id(), currentUser.get().getLoggableName())));
return true;
}
} catch (PermissionBackendException ex) {
@@ -880,6 +904,15 @@
};
}
+ private DebugMessage createDebugMessageForNonResolvableEmail(String email, String adminMessage) {
+ // for non-admins we cannot reveal why a code owner email cannot be resolved so that they are
+ // not able to probe whether an email exists or not
+ return DebugMessage.createMessage(
+ adminMessage,
+ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible", email));
+ }
+
/**
* Creates a map function that maps a {@code Pair<String, AccountState>} to a code owner.
*
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
index 4db3a4c..a973c8e 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
@@ -64,7 +64,7 @@
}
/** Gets messages that were collected while resolving the code owners. */
- public abstract ImmutableList<String> messages();
+ public abstract ImmutableList<DebugMessage> messages();
/**
* Whether there are any code owners defined for the path, regardless of whether they can be
@@ -98,7 +98,7 @@
boolean hasUnresolvedCodeOwners,
ImmutableList<CodeOwnerConfigImport> resolvedImports,
ImmutableList<CodeOwnerConfigImport> unresolvedImports,
- List<String> messages) {
+ List<DebugMessage> messages) {
return new AutoValue_CodeOwnerResolverResult(
codeOwners,
annotations,
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/DebugMessage.java b/java/com/google/gerrit/plugins/codeowners/backend/DebugMessage.java
new file mode 100644
index 0000000..9baa07b
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/backend/DebugMessage.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 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 java.util.Optional;
+
+/** A message to return debug information to callers. */
+@AutoValue
+public abstract class DebugMessage {
+ /**
+ * The debug message with all information.
+ *
+ * <p>Must be shown only to admin users (users with the 'Administrate Server' capability or the
+ * 'Check Code Owner' capability).
+ */
+ public abstract String adminMessage();
+
+ /**
+ * The debug message without information that require admin permissions.
+ *
+ * <p>Can be shown to the calling user.
+ *
+ * <p>Some messages are not available for the calling user. In this case {@link Optional#empty()}
+ * is returned.
+ */
+ public abstract Optional<String> userMessage();
+
+ /**
+ * Creates a debug message.
+ *
+ * @param adminMessage message that can only be shown to admins
+ */
+ public static DebugMessage createAdminOnlyMessage(String adminMessage) {
+ return new AutoValue_DebugMessage(adminMessage, Optional.empty());
+ }
+
+ /**
+ * Creates a debug message.
+ *
+ * @param adminMessage message that can only be shown to admins
+ * @param userMessage message that can be shown to the calling user
+ */
+ public static DebugMessage createMessage(String adminMessage, String userMessage) {
+ return new AutoValue_DebugMessage(adminMessage, Optional.of(userMessage));
+ }
+
+ /**
+ * Creates a debug message.
+ *
+ * @param userMessage message that can be shown to the calling user
+ */
+ public static DebugMessage createMessage(String userMessage) {
+ return new AutoValue_DebugMessage(userMessage, Optional.of(userMessage));
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/OptionalResultWithMessages.java b/java/com/google/gerrit/plugins/codeowners/backend/OptionalResultWithMessages.java
index 6775d5b..efff089 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/OptionalResultWithMessages.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/OptionalResultWithMessages.java
@@ -47,7 +47,7 @@
}
/** Gets the messages. */
- public abstract ImmutableList<String> messages();
+ public abstract ImmutableList<DebugMessage> messages();
/** Creates a {@link OptionalResultWithMessages} instance without messages. */
public static <T> OptionalResultWithMessages<T> create(T result) {
@@ -55,26 +55,26 @@
}
/** Creates an empty {@link OptionalResultWithMessages} instance with a single message. */
- public static <T> OptionalResultWithMessages<T> createEmpty(String message) {
+ public static <T> OptionalResultWithMessages<T> createEmpty(DebugMessage message) {
requireNonNull(message, "message");
return createEmpty(ImmutableList.of(message));
}
/** Creates an empty {@link OptionalResultWithMessages} instance with messages. */
- public static <T> OptionalResultWithMessages<T> createEmpty(List<String> messages) {
+ public static <T> OptionalResultWithMessages<T> createEmpty(List<DebugMessage> messages) {
requireNonNull(messages, "messages");
return new AutoValue_OptionalResultWithMessages<>(
Optional.empty(), ImmutableList.copyOf(messages));
}
/** Creates a {@link OptionalResultWithMessages} instance with messages. */
- public static <T> OptionalResultWithMessages<T> create(T result, String message) {
+ public static <T> OptionalResultWithMessages<T> create(T result, DebugMessage message) {
requireNonNull(message, "message");
return create(result, ImmutableList.of(message));
}
/** Creates a {@link OptionalResultWithMessages} instance with messages. */
- public static <T> OptionalResultWithMessages<T> create(T result, List<String> messages) {
+ public static <T> OptionalResultWithMessages<T> create(T result, List<DebugMessage> messages) {
requireNonNull(result, "result");
requireNonNull(messages, "messages");
return new AutoValue_OptionalResultWithMessages<>(
@@ -83,7 +83,7 @@
/** Creates a {@link OptionalResultWithMessages} instance with messages. */
public static <T> OptionalResultWithMessages<T> create(
- Optional<T> result, List<String> messages) {
+ Optional<T> result, List<DebugMessage> messages) {
requireNonNull(result, "result");
requireNonNull(messages, "messages");
return new AutoValue_OptionalResultWithMessages<>(result, ImmutableList.copyOf(messages));
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index 2083fc2..585c3e7 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -229,12 +229,13 @@
codeOwnerConfigFilePath);
pathCodeOwnersResultBuilder.addMessage(
- String.format(
- "resolve code owners for %s from code owner config %s:%s:%s",
- path,
- codeOwnerConfig.key().project(),
- codeOwnerConfig.key().shortBranchName(),
- codeOwnerConfigFilePath));
+ DebugMessage.createMessage(
+ String.format(
+ "resolve code owners for %s from code owner config %s:%s:%s",
+ path,
+ codeOwnerConfig.key().project(),
+ codeOwnerConfig.key().shortBranchName(),
+ codeOwnerConfigFilePath)));
// Add all data from the original code owner config that is relevant for the path
// (ignoreParentCodeOwners flag, global code owner sets and matching per-file code owner
@@ -246,9 +247,10 @@
getMatchingPerFileCodeOwnerSets(codeOwnerConfig).collect(toImmutableSet());
for (CodeOwnerSet codeOwnerSet : matchingPerFileCodeOwnerSets) {
pathCodeOwnersResultBuilder.addMessage(
- String.format(
- "per-file code owner set with path expressions %s matches",
- codeOwnerSet.pathExpressions()));
+ DebugMessage.createMessage(
+ String.format(
+ "per-file code owner set with path expressions %s matches",
+ codeOwnerSet.pathExpressions())));
pathCodeOwnersResultBuilder.addPerFileCodeOwnerSet(codeOwnerSet);
}
@@ -461,7 +463,7 @@
message = message.substring(0, message.length() - 1);
}
if (!message.isEmpty()) {
- pathCodeOwnersResultBuilder.addMessage(message);
+ pathCodeOwnersResultBuilder.addMessage(DebugMessage.createMessage(message));
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
index a124e3e..b0ab171 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
@@ -41,7 +41,7 @@
public abstract boolean ignoreParentCodeOwners();
/** Gets whether global code owners (aka folder code owners) should be ignored for the path. */
- abstract boolean ignoreGlobalCodeOwners();
+ public abstract boolean ignoreGlobalCodeOwners();
/** Gets code owner sets that contain global code owners (aka folder code owners). */
abstract ImmutableSet<CodeOwnerSet> globalCodeOwnerSets();
@@ -60,7 +60,7 @@
return !unresolvedImports().isEmpty();
}
- public abstract ImmutableList<String> messages();
+ public abstract ImmutableList<DebugMessage> messages();
/**
* Gets the code owners from the code owner config that apply to the path.
@@ -184,10 +184,11 @@
ignoreGlobalCodeOwners(true);
addMessage(
- String.format(
- "found matching per-file code owner set (with path expressions = %s) that ignores"
- + " parent code owners, hence ignoring the folder code owners",
- perFileCodeOwnerSet.pathExpressions()));
+ DebugMessage.createMessage(
+ String.format(
+ "found matching per-file code owner set (with path expressions = %s) that ignores"
+ + " parent code owners, hence ignoring the folder code owners",
+ perFileCodeOwnerSet.pathExpressions())));
}
}
@@ -235,17 +236,17 @@
return this;
}
- abstract ImmutableList.Builder<String> messagesBuilder();
+ abstract ImmutableList.Builder<DebugMessage> messagesBuilder();
@CanIgnoreReturnValue
- Builder addMessage(String message) {
+ Builder addMessage(DebugMessage message) {
requireNonNull(message, "message");
messagesBuilder().add(message);
return this;
}
@CanIgnoreReturnValue
- Builder addAllMessages(ImmutableList<String> messages) {
+ Builder addAllMessages(ImmutableList<DebugMessage> messages) {
requireNonNull(messages, "messages");
messagesBuilder().addAll(messages);
return this;
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
index d92f4fc..0e97a25 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
@@ -50,6 +50,7 @@
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerScore;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerScoring;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerScorings;
+import com.google.gerrit.plugins.codeowners.backend.DebugMessage;
import com.google.gerrit.plugins.codeowners.backend.Pair;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
@@ -219,7 +220,7 @@
Set<CodeOwner> codeOwners = new HashSet<>();
ListMultimap<CodeOwner, CodeOwnerAnnotation> annotations = LinkedListMultimap.create();
AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
- ImmutableList.Builder<String> debugLogsBuilder = ImmutableList.builder();
+ ImmutableList.Builder<DebugMessage> debugLogsBuilder = ImmutableList.builder();
ImmutableList.Builder<CodeOwnerConfigFileInfo> codeOwnerConfigFileInfosBuilder =
ImmutableList.builder();
codeOwnerConfigHierarchy.visit(
@@ -283,7 +284,7 @@
if (!ownedByAllUsers.get()) {
CodeOwnerResolverResult globalCodeOwners = getGlobalCodeOwners(rsrc.getBranch().project());
- debugLogsBuilder.add("resolve global code owners");
+ debugLogsBuilder.add(DebugMessage.createMessage("resolve global code owners"));
debugLogsBuilder.addAll(globalCodeOwners.messages());
globalCodeOwners
@@ -348,8 +349,11 @@
codeOwnersInfo.codeOwners = codeOwnersInfoList;
codeOwnersInfo.ownedByAllUsers = ownedByAllUsers.get() ? true : null;
codeOwnersInfo.codeOwnerConfigs = codeOwnerConfigFileInfosBuilder.build();
- ImmutableList<String> debugLogs = debugLogsBuilder.build();
- codeOwnersInfo.debugLogs = debug ? debugLogs : null;
+ ImmutableList<DebugMessage> debugLogs = debugLogsBuilder.build();
+ codeOwnersInfo.debugLogs =
+ debug
+ ? debugLogs.stream().map(DebugMessage::adminMessage).collect(toImmutableList())
+ : null;
logger.atFine().log("debug logs: %s", debugLogs);
return Response.ok(codeOwnersInfo);
@@ -402,7 +406,7 @@
R rsrc,
ImmutableMultimap<CodeOwner, CodeOwnerAnnotation> annotations,
ImmutableSet<CodeOwner> codeOwners,
- ImmutableList.Builder<String> debugLogs) {
+ ImmutableList.Builder<DebugMessage> debugLogs) {
return filterCodeOwners(rsrc, annotations, getVisibleCodeOwners(rsrc, codeOwners), debugLogs)
.collect(toImmutableSet());
}
@@ -420,7 +424,7 @@
R rsrc,
ImmutableMultimap<CodeOwner, CodeOwnerAnnotation> annotations,
Stream<CodeOwner> codeOwners,
- ImmutableList.Builder<String> debugLogs) {
+ ImmutableList.Builder<DebugMessage> debugLogs) {
return codeOwners;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java
index 84e5faf..303f906 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java
@@ -16,7 +16,6 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -29,6 +28,7 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.plugins.codeowners.api.CheckedCodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.backend.CodeOwner;
@@ -37,6 +37,7 @@
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
import com.google.gerrit.plugins.codeowners.backend.CodeOwners;
+import com.google.gerrit.plugins.codeowners.backend.DebugMessage;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
import com.google.gerrit.plugins.codeowners.backend.OptionalResultWithMessages;
import com.google.gerrit.plugins.codeowners.backend.PathCodeOwners;
@@ -158,10 +159,9 @@
validateInput(branchResource);
Path absolutePath = JgitPath.of(path).getAsAbsolutePath();
- ImmutableList.Builder<CodeOwnerConfigFileInfo> codeOwnerConfigFileInfosBuilder =
+ ImmutableList.Builder<CheckedCodeOwnerConfigFileInfo> checkedCodeOwnerConfigFileInfosBuilder =
ImmutableList.builder();
- List<String> messages = new ArrayList<>();
- List<Path> codeOwnerConfigFilePaths = new ArrayList<>();
+ List<DebugMessage> messages = new ArrayList<>();
AtomicBoolean isCodeOwnershipAssignedToEmail = new AtomicBoolean(false);
AtomicBoolean isCodeOwnershipAssignedToAllUsers = new AtomicBoolean(false);
AtomicBoolean isDefaultCodeOwner = new AtomicBoolean(false);
@@ -173,26 +173,28 @@
ObjectId.fromString(branchResource.getRevision().get()),
absolutePath,
codeOwnerConfig -> {
+ // Whether this code owner config file assigns code ownership to the user (either directly
+ // to the email or by making all users code owners).
+ boolean assignsCodeOwnershipToUser = false;
+
messages.add(
- String.format(
- "checking code owner config file %s", codeOwnerConfig.key().format(codeOwners)));
+ DebugMessage.createMessage(
+ String.format(
+ "checking code owner config file %s",
+ codeOwnerConfig.key().format(codeOwners))));
PathCodeOwnersResult pathCodeOwnersResult =
pathCodeOwnersFactory
.createWithoutCache(codeOwnerConfig, absolutePath)
.resolveCodeOwnerConfig();
- codeOwnerConfigFileInfosBuilder.add(
- codeOwnerConfigFileJson.format(
- codeOwnerConfig,
- pathCodeOwnersResult.resolvedImports(),
- pathCodeOwnersResult.unresolvedImports()));
-
messages.addAll(pathCodeOwnersResult.messages());
pathCodeOwnersResult
.unresolvedImports()
.forEach(
unresolvedImport ->
- messages.add(unresolvedImportFormatter.format(unresolvedImport)));
+ messages.add(
+ DebugMessage.createMessage(
+ unresolvedImportFormatter.format(unresolvedImport))));
Optional<CodeOwnerReference> codeOwnerReference =
pathCodeOwnersResult.getPathCodeOwners().stream()
.filter(cor -> cor.email().equals(email))
@@ -200,24 +202,29 @@
if (codeOwnerReference.isPresent()
&& !CodeOwnerResolver.ALL_USERS_WILDCARD.equals(email)) {
isCodeOwnershipAssignedToEmail.set(true);
+ assignsCodeOwnershipToUser = true;
if (RefNames.isConfigRef(codeOwnerConfig.key().ref())) {
messages.add(
- String.format(
- "found email %s as a code owner in the default code owner config", email));
+ DebugMessage.createMessage(
+ String.format(
+ "found email %s as a code owner in the default code owner config",
+ email)));
isDefaultCodeOwner.set(true);
} else {
Path codeOwnerConfigFilePath = codeOwners.getFilePath(codeOwnerConfig.key());
messages.add(
- String.format(
- "found email %s as a code owner in %s", email, codeOwnerConfigFilePath));
- codeOwnerConfigFilePaths.add(codeOwnerConfigFilePath);
+ DebugMessage.createMessage(
+ String.format(
+ "found email %s as a code owner in %s", email, codeOwnerConfigFilePath)));
}
ImmutableSet<String> localAnnotations = pathCodeOwnersResult.getAnnotationsFor(email);
if (!localAnnotations.isEmpty()) {
messages.add(
- String.format("email %s is annotated with %s", email, sort(localAnnotations)));
+ DebugMessage.createMessage(
+ String.format(
+ "email %s is annotated with %s", email, sort(localAnnotations))));
annotations.addAll(localAnnotations);
}
}
@@ -225,33 +232,34 @@
if (pathCodeOwnersResult.getPathCodeOwners().stream()
.anyMatch(cor -> cor.email().equals(CodeOwnerResolver.ALL_USERS_WILDCARD))) {
isCodeOwnershipAssignedToAllUsers.set(true);
+ assignsCodeOwnershipToUser = true;
if (RefNames.isConfigRef(codeOwnerConfig.key().ref())) {
messages.add(
- String.format(
- "found the all users wildcard ('%s') as a code owner in the default code"
- + " owner config which makes %s a code owner",
- CodeOwnerResolver.ALL_USERS_WILDCARD, email));
+ DebugMessage.createMessage(
+ String.format(
+ "found the all users wildcard ('%s') as a code owner in the default code"
+ + " owner config which makes %s a code owner",
+ CodeOwnerResolver.ALL_USERS_WILDCARD, email)));
isDefaultCodeOwner.set(true);
} else {
Path codeOwnerConfigFilePath = codeOwners.getFilePath(codeOwnerConfig.key());
messages.add(
- String.format(
- "found the all users wildcard ('%s') as a code owner in %s which makes %s a"
- + " code owner",
- CodeOwnerResolver.ALL_USERS_WILDCARD, codeOwnerConfigFilePath, email));
- if (!codeOwnerConfigFilePaths.contains(codeOwnerConfigFilePath)) {
- codeOwnerConfigFilePaths.add(codeOwnerConfigFilePath);
- }
+ DebugMessage.createMessage(
+ String.format(
+ "found the all users wildcard ('%s') as a code owner in %s which makes %s a"
+ + " code owner",
+ CodeOwnerResolver.ALL_USERS_WILDCARD, codeOwnerConfigFilePath, email)));
}
ImmutableSet<String> localAnnotations =
pathCodeOwnersResult.getAnnotationsFor(CodeOwnerResolver.ALL_USERS_WILDCARD);
if (!localAnnotations.isEmpty()) {
messages.add(
- String.format(
- "found annotations for the all users wildcard ('%s') which apply to %s: %s",
- CodeOwnerResolver.ALL_USERS_WILDCARD, email, sort(localAnnotations)));
+ DebugMessage.createMessage(
+ String.format(
+ "found annotations for the all users wildcard ('%s') which apply to %s: %s",
+ CodeOwnerResolver.ALL_USERS_WILDCARD, email, sort(localAnnotations))));
annotations.addAll(localAnnotations);
}
}
@@ -264,10 +272,22 @@
}
if (pathCodeOwnersResult.ignoreParentCodeOwners()) {
- messages.add("parent code owners are ignored");
+ messages.add(DebugMessage.createMessage("parent code owners are ignored"));
parentCodeOwnersAreIgnored.set(true);
}
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo =
+ codeOwnerConfigFileJson.format(
+ codeOwnerConfig,
+ pathCodeOwnersResult.resolvedImports(),
+ pathCodeOwnersResult.unresolvedImports());
+ checkedCodeOwnerConfigFileInfosBuilder.add(
+ CheckedCodeOwnerConfigFileJson.format(
+ codeOwnerConfigFileInfo,
+ assignsCodeOwnershipToUser,
+ pathCodeOwnersResult.ignoreParentCodeOwners(),
+ pathCodeOwnersResult.ignoreGlobalCodeOwners()));
+
return !pathCodeOwnersResult.ignoreParentCodeOwners();
});
@@ -275,15 +295,17 @@
if (isGlobalCodeOwner(branchResource.getNameKey(), email)) {
isGlobalCodeOwner = true;
- messages.add(String.format("found email %s as global code owner", email));
+ messages.add(
+ DebugMessage.createMessage(String.format("found email %s as global code owner", email)));
isCodeOwnershipAssignedToEmail.set(true);
}
if (isGlobalCodeOwner(branchResource.getNameKey(), CodeOwnerResolver.ALL_USERS_WILDCARD)) {
isGlobalCodeOwner = true;
messages.add(
- String.format(
- "found email %s as global code owner", CodeOwnerResolver.ALL_USERS_WILDCARD));
+ DebugMessage.createMessage(
+ String.format(
+ "found email %s as global code owner", CodeOwnerResolver.ALL_USERS_WILDCARD)));
isCodeOwnershipAssignedToAllUsers.set(true);
}
@@ -324,8 +346,10 @@
.collect(toImmutableSet());
if (!unsupportedAnnotations.isEmpty()) {
messages.add(
- String.format(
- "dropping unsupported annotations for %s: %s", email, sort(unsupportedAnnotations)));
+ DebugMessage.createMessage(
+ String.format(
+ "dropping unsupported annotations for %s: %s",
+ email, sort(unsupportedAnnotations))));
annotations.removeAll(unsupportedAnnotations);
}
@@ -343,18 +367,17 @@
|| isFallbackCodeOwner)
&& isResolvable;
codeOwnerCheckInfo.isResolvable = isResolvable;
- codeOwnerCheckInfo.codeOwnerConfigs = codeOwnerConfigFileInfosBuilder.build();
+ codeOwnerCheckInfo.checkedCodeOwnerConfigs = checkedCodeOwnerConfigFileInfosBuilder.build();
codeOwnerCheckInfo.canReadRef = canReadRef;
codeOwnerCheckInfo.canSeeChange = canSeeChange;
codeOwnerCheckInfo.canApproveChange = canApproveChange;
- codeOwnerCheckInfo.codeOwnerConfigFilePaths =
- codeOwnerConfigFilePaths.stream().map(Path::toString).collect(toList());
codeOwnerCheckInfo.isFallbackCodeOwner = isFallbackCodeOwner && isResolvable;
codeOwnerCheckInfo.isDefaultCodeOwner = isDefaultCodeOwner.get();
codeOwnerCheckInfo.isGlobalCodeOwner = isGlobalCodeOwner;
codeOwnerCheckInfo.isOwnedByAllUsers = isCodeOwnershipAssignedToAllUsers.get();
codeOwnerCheckInfo.annotations = sort(annotations);
- codeOwnerCheckInfo.debugLogs = messages;
+ codeOwnerCheckInfo.debugLogs =
+ messages.stream().map(DebugMessage::adminMessage).collect(toImmutableList());
return Response.ok(codeOwnerCheckInfo);
}
@@ -431,8 +454,8 @@
OptionalResultWithMessages<CodeOwner> resolveResult =
codeOwnerResolver.resolveWithMessages(CodeOwnerReference.create(email));
- List<String> messages = new ArrayList<>();
- messages.add(String.format("trying to resolve email %s", email));
+ List<DebugMessage> messages = new ArrayList<>();
+ messages.add(DebugMessage.createMessage(String.format("trying to resolve email %s", email)));
messages.addAll(resolveResult.messages());
if (resolveResult.isPresent()) {
return OptionalResultWithMessages.create(resolveResult.get(), messages);
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckedCodeOwnerConfigFileJson.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckedCodeOwnerConfigFileJson.java
new file mode 100644
index 0000000..8c5d516
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckedCodeOwnerConfigFileJson.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 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 com.google.gerrit.plugins.codeowners.api.CheckedCodeOwnerConfigFileInfo;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
+
+/** Collection of routines to populate {@link CheckedCodeOwnerConfigFileInfo}. */
+public class CheckedCodeOwnerConfigFileJson {
+ public static CheckedCodeOwnerConfigFileInfo format(
+ CodeOwnerConfigFileInfo codeOwnerConfigFileInfo,
+ boolean assignsCodeOwnershipToUser,
+ boolean areParentCodeOwnersIgnored,
+ boolean areFolderCodeOwnersIgnored) {
+ CheckedCodeOwnerConfigFileInfo info = new CheckedCodeOwnerConfigFileInfo();
+ info.codeOwnerConfigFileInfo = codeOwnerConfigFileInfo;
+ info.assignsCodeOwnershipToUser = assignsCodeOwnershipToUser;
+ info.areParentCodeOwnersIgnored = areParentCodeOwnersIgnored;
+ info.areFolderCodeOwnersIgnored = areFolderCodeOwnersIgnored;
+ return info;
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
index bcd0072..2ee456d 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
@@ -34,6 +34,7 @@
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerScore;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerScoring;
+import com.google.gerrit.plugins.codeowners.backend.DebugMessage;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.account.AccountControl;
@@ -143,7 +144,7 @@
CodeOwnersInChangeCollection.PathResource rsrc,
ImmutableMultimap<CodeOwner, CodeOwnerAnnotation> annotations,
Stream<CodeOwner> codeOwners,
- ImmutableList.Builder<String> debugLogs) {
+ ImmutableList.Builder<DebugMessage> debugLogs) {
// The change owner and service users should never be suggested, hence filter them out.
ImmutableList<CodeOwner> filteredCodeOwners =
@@ -171,14 +172,17 @@
}
private Predicate<CodeOwner> filterOutChangeOwner(
- CodeOwnersInChangeCollection.PathResource rsrc, ImmutableList.Builder<String> debugLogs) {
+ CodeOwnersInChangeCollection.PathResource rsrc,
+ ImmutableList.Builder<DebugMessage> debugLogs) {
return codeOwner -> {
if (!codeOwner.accountId().equals(rsrc.getRevisionResource().getChange().getOwner())) {
// Returning true from the Predicate here means that the code owner should be kept.
return true;
}
debugLogs.add(
- String.format("filtering out %s because this code owner is the change owner", codeOwner));
+ DebugMessage.createMessage(
+ String.format(
+ "filtering out %s because this code owner is the change owner", codeOwner)));
// Returning false from the Predicate here means that the code owner should be filtered out.
return false;
};
@@ -187,7 +191,7 @@
private Predicate<CodeOwner> filterOutCodeOwnersThatAreAnnotatedWithLastResortSuggestion(
CodeOwnersInChangeCollection.PathResource rsrc,
ImmutableMultimap<CodeOwner, CodeOwnerAnnotation> annotations,
- ImmutableList.Builder<String> debugLogs) {
+ ImmutableList.Builder<DebugMessage> debugLogs) {
return codeOwner -> {
boolean lastResortSuggestion =
annotations.containsEntry(
@@ -198,9 +202,10 @@
if (isReviewer(rsrc, codeOwner)) {
if (lastResortSuggestion) {
debugLogs.add(
- String.format(
- "ignoring %s annotation for %s because this code owner is a reviewer",
- CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key(), codeOwner));
+ DebugMessage.createMessage(
+ String.format(
+ "ignoring %s annotation for %s because this code owner is a reviewer",
+ CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key(), codeOwner)));
}
// Returning true from the Predicate here means that the code owner should be kept.
@@ -211,9 +216,10 @@
return true;
}
debugLogs.add(
- String.format(
- "filtering out %s because this code owner is annotated with %s",
- codeOwner, CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key()));
+ DebugMessage.createMessage(
+ String.format(
+ "filtering out %s because this code owner is annotated with %s",
+ codeOwner, CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key())));
// Returning false from the Predicate here means that the code owner should be filtered out.
return false;
};
@@ -227,7 +233,8 @@
.contains(codeOwner.accountId());
}
- private Predicate<CodeOwner> filterOutServiceUsers(ImmutableList.Builder<String> debugLogs) {
+ private Predicate<CodeOwner> filterOutServiceUsers(
+ ImmutableList.Builder<DebugMessage> debugLogs) {
if (!cfg.getBoolean(
"suggest", "skipServiceUsers", SuggestReviewers.DEFAULT_SKIP_SERVICE_USERS)) {
// Returning true from the Predicate here means that the code owner should not be filtered
@@ -241,7 +248,9 @@
return true;
}
debugLogs.add(
- String.format("filtering out %s because this code owner is a service user", codeOwner));
+ DebugMessage.createMessage(
+ String.format(
+ "filtering out %s because this code owner is a service user", codeOwner)));
// Returning false from the Predicate here means that the code owner should be filtered out.
return false;
};
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CheckedCodeOwnerConfigFileInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CheckedCodeOwnerConfigFileInfoSubject.java
new file mode 100644
index 0000000..1821534
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CheckedCodeOwnerConfigFileInfoSubject.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2024 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.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject.codeOwnerConfigFileInfos;
+
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.gerrit.plugins.codeowners.api.CheckedCodeOwnerConfigFileInfo;
+
+public class CheckedCodeOwnerConfigFileInfoSubject extends Subject {
+ /**
+ * Starts fluent chain to do assertions on a {@link CheckedCodeOwnerConfigFileInfo}.
+ *
+ * @param checkedCodeOwnerConfigFileInfo the checked code owner config file info on which
+ * assertions should be done
+ * @return the created {@link CheckedCodeOwnerConfigFileInfoSubject}
+ */
+ public static CheckedCodeOwnerConfigFileInfoSubject assertThat(
+ CheckedCodeOwnerConfigFileInfo checkedCodeOwnerConfigFileInfo) {
+ return assertAbout(checkedCodeOwnerConfigFileInfos()).that(checkedCodeOwnerConfigFileInfo);
+ }
+
+ public static Factory<CheckedCodeOwnerConfigFileInfoSubject, CheckedCodeOwnerConfigFileInfo>
+ checkedCodeOwnerConfigFileInfos() {
+ return CheckedCodeOwnerConfigFileInfoSubject::new;
+ }
+
+ private final CheckedCodeOwnerConfigFileInfo checkedCodeOwnerConfigFileInfo;
+
+ private CheckedCodeOwnerConfigFileInfoSubject(
+ FailureMetadata metadata, CheckedCodeOwnerConfigFileInfo checkedCodeOwnerConfigFileInfo) {
+ super(metadata, checkedCodeOwnerConfigFileInfo);
+ this.checkedCodeOwnerConfigFileInfo = checkedCodeOwnerConfigFileInfo;
+ }
+
+ public CodeOwnerConfigFileInfoSubject hasCodeOwnerConfigFileThat() {
+ return check("codeOwnerConfig")
+ .about(codeOwnerConfigFileInfos())
+ .that(checkedCodeOwnerConfigFileInfo().codeOwnerConfigFileInfo);
+ }
+
+ public BooleanSubject hasAssignsCodeOwnershipToUserThat() {
+ return check("assignsCodeOwnershipToUser()")
+ .that(checkedCodeOwnerConfigFileInfo().assignsCodeOwnershipToUser);
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject assignsCodeOwnershipToUser() {
+ hasAssignsCodeOwnershipToUserThat().isTrue();
+ return this;
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject doesNotAssignCodeOwnershipToUser() {
+ hasAssignsCodeOwnershipToUserThat().isFalse();
+ return this;
+ }
+
+ public BooleanSubject hasAreParentCodeOwnersIgnoredThat() {
+ return check("areParentCodeOwnersIgnored()")
+ .that(checkedCodeOwnerConfigFileInfo().areParentCodeOwnersIgnored);
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject parentCodeOwnersAreIgnored() {
+ hasAreParentCodeOwnersIgnoredThat().isTrue();
+ return this;
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject parentCodeOwnersAreNotIgnored() {
+ hasAreParentCodeOwnersIgnoredThat().isFalse();
+ return this;
+ }
+
+ public BooleanSubject hasAreFolderCodeOwnersIgnoredThat() {
+ return check("areFolderCodeOwnersIgnored()")
+ .that(checkedCodeOwnerConfigFileInfo().areFolderCodeOwnersIgnored);
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject folderCodeOwnersAreIgnored() {
+ hasAreFolderCodeOwnersIgnoredThat().isTrue();
+ return this;
+ }
+
+ public CheckedCodeOwnerConfigFileInfoSubject folderCodeOwnersAreNotIgnored() {
+ hasAreFolderCodeOwnersIgnoredThat().isFalse();
+ return this;
+ }
+
+ private CheckedCodeOwnerConfigFileInfo checkedCodeOwnerConfigFileInfo() {
+ isNotNull();
+ return checkedCodeOwnerConfigFileInfo;
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
index 532fdd7..ecf5a72 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
@@ -15,14 +15,14 @@
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.CheckedCodeOwnerConfigFileInfoSubject.checkedCodeOwnerConfigFileInfos;
import static com.google.gerrit.truth.ListSubject.elements;
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.CheckedCodeOwnerConfigFileInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo;
-import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo;
import com.google.gerrit.truth.ListSubject;
/** {@link Subject} for doing assertions on {@link CodeOwnerCheckInfo}s. */
@@ -74,11 +74,12 @@
}
/** Returns a {@link ListSubject} for the code owner config file infos. */
- public ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo>
- hasCodeOwnerConfigsThat() {
+ public ListSubject<CheckedCodeOwnerConfigFileInfoSubject, CheckedCodeOwnerConfigFileInfo>
+ hasCheckedCodeOwnerConfigsThat() {
return check("codeOwnerConfigs")
.about(elements())
- .thatCustom(codeOwnerCheckInfo().codeOwnerConfigs, codeOwnerConfigFileInfos());
+ .thatCustom(
+ codeOwnerCheckInfo().checkedCodeOwnerConfigs, checkedCodeOwnerConfigFileInfos());
}
public void canReadRef() {
@@ -117,10 +118,6 @@
check("canApproveChange").that(codeOwnerCheckInfo().canApproveChange).isNull();
}
- public IterableSubject hasCodeOwnerConfigFilePathsThat() {
- return check("codeOwnerConfigFilePaths").that(codeOwnerCheckInfo().codeOwnerConfigFilePaths);
- }
-
public void isDefaultCodeOwner() {
check("isDefaultCodeOwner").that(codeOwnerCheckInfo().isDefaultCodeOwner).isTrue();
}
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
index 67ddce7..d6f6fa8 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
@@ -49,7 +49,7 @@
private final CodeOwnerConfigFileInfo codeOwnerConfigFileInfo;
- private CodeOwnerConfigFileInfoSubject(
+ protected CodeOwnerConfigFileInfoSubject(
FailureMetadata metadata, CodeOwnerConfigFileInfo codeOwnerConfigFileInfo) {
super(metadata, codeOwnerConfigFileInfo);
this.codeOwnerConfigFileInfo = codeOwnerConfigFileInfo;
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/OptionalResultWithMessagesSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/OptionalResultWithMessagesSubject.java
index 8d46152..0f6cf34 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/OptionalResultWithMessagesSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/OptionalResultWithMessagesSubject.java
@@ -14,13 +14,16 @@
package com.google.gerrit.plugins.codeowners.testing;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.gerrit.truth.OptionalSubject.optionals;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.Subject;
+import com.google.gerrit.plugins.codeowners.backend.DebugMessage;
import com.google.gerrit.plugins.codeowners.backend.OptionalResultWithMessages;
+import java.util.Optional;
/** {@link Subject} for doing assertions on {@link OptionalResultWithMessages}s. */
public class OptionalResultWithMessagesSubject extends Subject {
@@ -57,8 +60,48 @@
check("result()").about(optionals()).that(optionalResultWithMessages().result()).isEmpty();
}
- public IterableSubject hasMessagesThat() {
- return check("messages()").that(optionalResultWithMessages().messages());
+ public OptionalResultWithMessagesSubject assertContainsAdminOnlyMessage(
+ String expectedAdminMessage) {
+ hasAdminMessagesThat().contains(expectedAdminMessage);
+ hasUserMessagesThat().doesNotContain(expectedAdminMessage);
+ return this;
+ }
+
+ public OptionalResultWithMessagesSubject assertContainsMessage(String expectedMessage) {
+ hasAdminMessagesThat().contains(expectedMessage);
+ hasUserMessagesThat().contains(expectedMessage);
+ return this;
+ }
+
+ public OptionalResultWithMessagesSubject assertContainsMessage(
+ String expectedAdminMessage, String expectedUserMessage) {
+ hasAdminMessagesThat().contains(expectedAdminMessage);
+ hasUserMessagesThat().contains(expectedUserMessage);
+ return this;
+ }
+
+ public OptionalResultWithMessagesSubject assertContainsExactlyMessage(String expectedMessage) {
+ hasAdminMessagesThat().containsExactly(expectedMessage);
+ hasUserMessagesThat().containsExactly(expectedMessage);
+ return this;
+ }
+
+ public IterableSubject hasAdminMessagesThat() {
+ return check("messages()")
+ .that(
+ optionalResultWithMessages().messages().stream()
+ .map(DebugMessage::adminMessage)
+ .collect(toImmutableList()));
+ }
+
+ public IterableSubject hasUserMessagesThat() {
+ return check("messages()")
+ .that(
+ optionalResultWithMessages().messages().stream()
+ .map(DebugMessage::userMessage)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableList()));
}
private OptionalResultWithMessages<?> optionalResultWithMessages() {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
index fe449a4..05a29bf 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
@@ -59,6 +59,7 @@
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.testing.CheckedCodeOwnerConfigFileInfoSubject;
import com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject;
import com.google.gerrit.plugins.codeowners.util.JgitPath;
import com.google.gerrit.server.ServerInitiated;
@@ -170,8 +171,12 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
@@ -180,9 +185,6 @@
assertThat(checkCodeOwnerInfo).canReadRef();
assertThat(checkCodeOwnerInfo).canSeeChangeNotSet();
assertThat(checkCodeOwnerInfo).canApproveChangeNotSet();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -212,39 +214,44 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().hasSize(3);
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().hasSize(3);
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.element(0)
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, fooBarCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.element(1)
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.element(2)
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, rootCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(
- getCodeOwnerConfigFilePath("/foo/bar/"),
- getCodeOwnerConfigFilePath("/foo/"),
- getCodeOwnerConfigFilePath(ROOT_PATH))
- .inOrder();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -286,29 +293,32 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().hasSize(2);
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().hasSize(2);
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.element(0)
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, fooBarCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.element(1)
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(
- getCodeOwnerConfigFilePath("/foo/bar/"), getCodeOwnerConfigFilePath("/foo/"))
- .inOrder();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -342,16 +352,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -376,16 +387,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isOwnedByAllUsers();
@@ -412,16 +424,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isOwnedByAllUsers();
@@ -443,8 +456,7 @@
CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email());
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -463,16 +475,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -500,16 +513,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -540,16 +554,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -579,16 +594,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -619,16 +635,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -661,16 +678,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -692,11 +710,10 @@
checkCodeOwner(ROOT_PATH, CodeOwnerResolver.ALL_USERS_WILDCARD);
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty();
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().isEmpty();
assertThat(checkCodeOwnerInfo).canReadRefNotSet();
assertThat(checkCodeOwnerInfo).canSeeChangeNotSet();
assertThat(checkCodeOwnerInfo).canApproveChangeNotSet();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -712,16 +729,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isOwnedByAllUsers();
@@ -750,14 +768,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -787,14 +808,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isOwnedByAllUsers();
@@ -823,8 +847,7 @@
CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email());
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -851,8 +874,7 @@
CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email());
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().isEmpty();
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isOwnedByAllUsers();
@@ -876,16 +898,17 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -923,16 +946,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -964,16 +988,17 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isNotResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH));
assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner();
assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner();
assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers();
@@ -1027,7 +1052,10 @@
CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email());
CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
+ assertThat(checkCodeOwnerInfo)
+ .hasCheckedCodeOwnerConfigsThat()
+ .onlyElement()
+ .hasCodeOwnerConfigFileThat();
codeOwnerConfigFileInfoSubject.assertKey(backend, codeOwnerConfigKey);
codeOwnerConfigFileInfoSubject
.assertNoWebLinks()
@@ -1176,7 +1204,10 @@
CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email());
CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
+ assertThat(checkCodeOwnerInfo)
+ .hasCheckedCodeOwnerConfigsThat()
+ .onlyElement()
+ .hasCodeOwnerConfigFileThat();
codeOwnerConfigFileInfoSubject.assertKey(backend, codeOwnerConfigKey);
codeOwnerConfigFileInfoSubject
.assertNoWebLinks()
@@ -1264,17 +1295,18 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
- assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
"per-file code owner set with path expressions [%s] matches",
@@ -1326,14 +1358,15 @@
assertThat(checkCodeOwnerInfo).isNotCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .doesNotAssignCodeOwnershipToUser()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
@@ -1348,17 +1381,18 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigsThat()
+ .hasCheckedCodeOwnerConfigsThat()
.onlyElement()
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreIgnored()
+ .folderCodeOwnersAreIgnored()
+ .hasCodeOwnerConfigFileThat()
.assertKey(backend, codeOwnerConfigKey)
.assertNoWebLinks()
.assertNoImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
- assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
"per-file code owner set with path expressions [%s] matches",
@@ -1413,17 +1447,21 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
- codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey);
+ CheckedCodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().onlyElement();
codeOwnerConfigFileInfoSubject
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
+ .assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoUnresolvedImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
CodeOwnerConfigFileInfoSubject importSubject =
- codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement();
+ codeOwnerConfigFileInfoSubject.hasCodeOwnerConfigFileThat().hasImportsThat().onlyElement();
importSubject.assertKey(backend, barCodeOwnerConfigKey);
importSubject.assertImportMode(barCodeOwnerConfigReference.importMode());
importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage();
@@ -1435,9 +1473,6 @@
transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
- assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
"Code owner config %s:%s:/foo/%s imports:\n"
@@ -1499,17 +1534,21 @@
assertThat(checkCodeOwnerInfo).isCodeOwner();
assertThat(checkCodeOwnerInfo).isResolvable();
- CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
- codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey);
+ CheckedCodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject =
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().onlyElement();
codeOwnerConfigFileInfoSubject
+ .assignsCodeOwnershipToUser()
+ .parentCodeOwnersAreNotIgnored()
+ .folderCodeOwnersAreNotIgnored()
+ .hasCodeOwnerConfigFileThat()
+ .assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoUnresolvedImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
CodeOwnerConfigFileInfoSubject importSubject =
- codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement();
+ codeOwnerConfigFileInfoSubject.hasCodeOwnerConfigFileThat().hasImportsThat().onlyElement();
importSubject.assertKey(backend, barCodeOwnerConfigKey);
importSubject.assertImportMode(barCodeOwnerConfigReference.importMode());
importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage();
@@ -1521,9 +1560,6 @@
transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage();
assertThat(checkCodeOwnerInfo)
- .hasCodeOwnerConfigFilePathsThat()
- .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
- assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
"Code owner config %s:%s:/foo/%s imports:\n"
@@ -1550,15 +1586,18 @@
assertThat(checkCodeOwnerInfo).isResolvable();
codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
- codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey);
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().onlyElement();
codeOwnerConfigFileInfoSubject
+ .doesNotAssignCodeOwnershipToUser()
+ .hasCodeOwnerConfigFileThat()
+ .assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoUnresolvedImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- importSubject = codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement();
+ importSubject =
+ codeOwnerConfigFileInfoSubject.hasCodeOwnerConfigFileThat().hasImportsThat().onlyElement();
importSubject.assertKey(backend, barCodeOwnerConfigKey);
importSubject.assertImportMode(barCodeOwnerConfigReference.importMode());
importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage();
@@ -1568,7 +1607,6 @@
transitiveImportSubject.assertImportMode(bazCodeOwnerConfigReference.importMode());
transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
@@ -1595,20 +1633,22 @@
assertThat(checkCodeOwnerInfo).isResolvable();
codeOwnerConfigFileInfoSubject =
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement();
- codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey);
+ assertThat(checkCodeOwnerInfo).hasCheckedCodeOwnerConfigsThat().onlyElement();
codeOwnerConfigFileInfoSubject
+ .doesNotAssignCodeOwnershipToUser()
+ .hasCodeOwnerConfigFileThat()
+ .assertKey(backend, fooCodeOwnerConfigKey)
.assertNoWebLinks()
.assertNoUnresolvedImports()
.assertNoUnresolvedErrorMessage()
.assertNoImportMode();
- importSubject = codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement();
+ importSubject =
+ codeOwnerConfigFileInfoSubject.hasCodeOwnerConfigFileThat().hasImportsThat().onlyElement();
importSubject.assertKey(backend, barCodeOwnerConfigKey);
importSubject.assertImportMode(barCodeOwnerConfigReference.importMode());
importSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage();
- assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
assertThat(checkCodeOwnerInfo)
.hasDebugLogsThatContainAllOf(
String.format(
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
index febdf6b..ed1d6c8 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
@@ -50,7 +50,7 @@
CodeOwnerConfig.Key.create(project, "master", "/bar/"),
CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"),
"test message")),
- ImmutableList.of("test message"));
+ ImmutableList.of(DebugMessage.createMessage("test message")));
assertThatToStringIncludesAllData(codeOwnerResolverResult, CodeOwnerResolverResult.class);
}
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
index e64c53e..7fc6cec 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
@@ -93,10 +93,12 @@
.resolveWithMessages(CodeOwnerReference.create(nonExistingEmail));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
- String.format(
+ .assertContainsMessage(
+ /* adminMessage= */ String.format(
"cannot resolve code owner email %s: no account with this email exists",
+ nonExistingEmail),
+ /* userMessage= */ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible",
nonExistingEmail));
}
@@ -108,8 +110,8 @@
.resolveWithMessages(CodeOwnerReference.create(admin.email()));
assertThat(result.get()).hasAccountIdThat().isEqualTo(admin.id());
assertThat(result)
- .hasMessagesThat()
- .contains(String.format("account %s is visible to user %s", admin.id(), admin.username()));
+ .assertContainsMessage(
+ String.format("account %s is visible to user %s", admin.id(), admin.username()));
}
@Test
@@ -120,8 +122,7 @@
.resolveWithMessages(CodeOwnerReference.create(CodeOwnerResolver.ALL_USERS_WILDCARD));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsMessage(
String.format(
"cannot resolve code owner email %s: no account with this email exists",
CodeOwnerResolver.ALL_USERS_WILDCARD));
@@ -170,9 +171,12 @@
.resolveWithMessages(CodeOwnerReference.create(admin.email()));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
- String.format("cannot resolve code owner email %s: email is ambiguous", admin.email()));
+ .assertContainsMessage(
+ /* adminMessage= */ String.format(
+ "cannot resolve code owner email %s: email is ambiguous", admin.email()),
+ /* userMessage= */ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible",
+ admin.email()));
}
@Test
@@ -191,13 +195,12 @@
codeOwnerResolverProvider.get().resolveWithMessages(CodeOwnerReference.create(email));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .containsAnyOf(
- String.format(
+ .assertContainsMessage(
+ /* adminMessage= */ String.format(
"cannot resolve account %s for email %s: account does not exists",
accountId, email),
- String.format(
- "cannost resolve code owner email %s: no active account with this email found",
+ /* userMessage= */ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible",
email));
}
@@ -210,8 +213,7 @@
.resolveWithMessages(CodeOwnerReference.create(user.email()));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsMessage(
String.format("ignoring inactive account %s for email %s", user.id(), user.email()));
}
@@ -231,11 +233,13 @@
.resolveWithMessages(CodeOwnerReference.create(admin.email()));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
- String.format(
+ .assertContainsMessage(
+ /* adminMessage= */ String.format(
"cannot resolve code owner email %s: account %s is not visible to user %s",
- admin.email(), admin.id(), user2.username()));
+ admin.email(), admin.id(), user2.username()),
+ /* userMessage= */ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible",
+ admin.email()));
}
@Test
@@ -254,8 +258,7 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result.get()).hasAccountIdThat().isEqualTo(user.id());
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsMessage(
String.format(
"resolved code owner email %s: account %s is referenced by secondary email and the"
+ " calling user %s can see secondary emails",
@@ -271,8 +274,7 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result.get()).hasAccountIdThat().isEqualTo(user.id());
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsAdminOnlyMessage(
String.format(
"resolved code owner email %s: account %s is referenced by secondary email and user"
+ " %s can see secondary emails",
@@ -286,8 +288,7 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result.get()).hasAccountIdThat().isEqualTo(user.id());
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsMessage(
String.format(
"email %s is visible to the calling user %s: email is a secondary email that is"
+ " owned by this user",
@@ -302,8 +303,7 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result.get()).hasAccountIdThat().isEqualTo(user.id());
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsAdminOnlyMessage(
String.format(
"email %s is visible to user %s: email is a secondary email that is owned by this"
+ " user",
@@ -325,12 +325,14 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
- String.format(
+ .assertContainsMessage(
+ /* adminMessage= */ String.format(
"cannot resolve code owner email %s: account %s is referenced by secondary email"
+ " but the calling user %s cannot see secondary emails",
- secondaryEmail, admin.id(), user.username()));
+ secondaryEmail, admin.id(), user.username()),
+ /* userMessage= */ String.format(
+ "cannot resolve code owner email %s: email doesn't exist or is not visible",
+ secondaryEmail));
// user doesn't have the "Modify Account" global capability and hence cannot see the secondary
// email of the admin account if another user is the calling user
@@ -342,8 +344,7 @@
.resolveWithMessages(CodeOwnerReference.create(secondaryEmail));
assertThat(result).isEmpty();
assertThat(result)
- .hasMessagesThat()
- .contains(
+ .assertContainsAdminOnlyMessage(
String.format(
"cannot resolve code owner email %s: account %s is referenced by secondary email"
+ " but user %s cannot see secondary emails",
@@ -681,7 +682,7 @@
OptionalResultWithMessages<Boolean> isEmailDomainAllowedResult =
codeOwnerResolverProvider.get().isEmailDomainAllowed(email);
assertThat(isEmailDomainAllowedResult.get()).isEqualTo(expectedResult);
- assertThat(isEmailDomainAllowedResult.messages()).containsExactly(expectedMessage);
+ assertThat(isEmailDomainAllowedResult).assertContainsExactlyMessage(expectedMessage);
}
@Test
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index cd19fac..015cb9e 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -301,17 +301,17 @@
{
"is_code_owner": false,
"is_resolvable": false,
- "code_owner_configs": [
+ "checked_code_owner_configs": [
{
- "project": "foo/bar",
- "branch": "master",
- "path": "/OWNERS"
+ "code_owner_config": {
+ "project": "foo/bar",
+ "branch": "master",
+ "path": "/OWNERS"
+ },
+ "assigns_code_ownership_to_user": true
}
]
"can_read_ref": true,
- "code_owner_config_file_paths": [
- "/OWNERS",
- ],
"is_fallback_code_owner": false,
"is_default_code_owner": false,
"is_global_code_owner": false,
@@ -950,6 +950,19 @@
---
+### <a id="checked-code-owner-config-file-info"> CheckedCodeOwnerConfigFileInfo
+The `CheckedCodeOwnerConfigFileInfo` entity contains information about a code
+owner config file and results of checking it.
+
+| Field Name | Description |
+| ----------- | ----------- |
+| `code_owner_config` | The code owner config file as a [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entity.
+| `assigns_code_ownership_to_user` | Whether this code owner config file assigns code ownership to the specified email and path. Note that if code ownership is assigned to the email via a code owner config file, but the email is not resolvable (see the `is_resolvable` field in [CodeOwnerCheckInfo](#code-owner-check-info)), the user is not a code owner.
+| `are_parent_code_owners_ignored` | Whether code owners from parent directory are ignored.
+| `are_folder_code_owners_ignored` | Whether folder code owners are ignored (i.e. whether there is a matching per-file rule that ignores folder code owners, e.g. by using "set noparent").
+
+---
+
### <a id="check-code-owner-config-files-in-revision-input"> CheckCodeOwnerConfigFilesInRevisionInput
The `CheckCodeOwnerConfigFilesInRevisionInput` allows to set options for the
[Check Code Owner Config Files In Revision REST endpoint](#check-code-owner-config-files-in-revision).
@@ -966,13 +979,12 @@
| Field Name | Description |
| --------------- | ----------- |
-| `is_code_owner` | Whether the given email owns the specified path in the branch. True if: a) the given email is resolvable (see field `is_resolvable') and b) any code owner config file assigns codeownership to the email for the path (see field `code_owner_config_file_paths`) or the email is configured as default code owner (see field `is_default_code_owner` or the email is configured as global code owner (see field `is_global_code_owner`) or the user is a fallback code owner (see field `is_fallback_code_owner`).
+| `is_code_owner` | Whether the given email owns the specified path in the branch. True if: a) the given email is resolvable (see field `is_resolvable') and b) any code owner config file assigns codeownership to the email for the path (see `assigns_code_ownership_to_user` field of the inspected code owner configs that are return in the `code_owner_configs` field) or the email is configured as global code owner (see field `is_global_code_owner`) or the user is a fallback code owner (see field `is_fallback_code_owner`).
| `is_resolvable` | Whether the given email is resolvable for the specified user or the calling user if no user was specified.
-| `code_owner_configs` | The code owner config files that have been inspected to check the code owner as [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entities.
+| `checked_code_owner_configs` | The code owner config files that have been inspected to check the code owner with check results as [CheckedCodeOwnerConfigFileInfo](#checked-code-owner-config-file-info) entities.
| `can_read_ref` | Whether the user to which the given email was resolved has read permissions on the branch. Not set if the given email is not resolvable or if the given email is the all users wildcard (aka '*').
| `can_see_change`| Whether the user to which the given email was resolved can see the specified change. Not set if the given email is not resolvable, if the given email is the all users wildcard (aka '*') or if no change was specified.
| `can_approve_change`| Whether the user to which the given email was resolved can code-owner approve the specified change. Being able to code-owner approve the change means that the user has permissions to vote on the label that is [required as code owner approval](config.html#pluginCodeOwnersRequiredApproval). Other permissions are not considered for computing this flag. In particular missing read permissions on the change don't have any effect on this flag. Whether the user misses read permissions on the change (and hence cannot apply the code owner approval) can be seen from the `can_see_change` flag. Not set if the given email is not resolvable, if the given email is the all users wildcard (aka '*') or if no change was specified.
-| `code_owner_config_file_paths` | Paths of the code owner config files that assign code ownership to the specified email and path as a list. Note that if code ownership is assigned to the email via a code owner config files, but the email is not resolvable (see field `is_resolvable` field), the user is not a code owner.
| `is_fallback_code_owner` | Whether the given email is a fallback code owner of the specified path in the branch. True if: a) the given email is resolvable (see field `is_resolvable') and b) no code owners are defined for the specified path in the branch and c) parent code owners are not ignored and d) the user is a fallback code owner according to the [configured fallback code owner policy](config.html#pluginCodeOwnersFallbackCodeOwners)
| `is_default_code_owner` | Whether the given email is configured as a default code owner in the code owner config file in `refs/meta/config`. Note that if the email is configured as default code owner, but the email is not resolvable (see `is_resolvable` field), the user is not a code owner.
| `is_global_code_owner` | Whether the given email is configured as a global code owner. Note that if the email is configured as global code owner, but the email is not resolvable (see `is_resolvable` field), the user is not a code owner.