Merge "Fix spelling of resolve-all-users parameter"
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
index e571724..4dc69b0 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
@@ -15,6 +15,8 @@
package com.google.gerrit.plugins.codeowners.api;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
+import com.google.gerrit.plugins.codeowners.common.CodeOwnerConfigValidationPolicy;
+import com.google.gerrit.plugins.codeowners.common.MergeCommitStrategy;
import java.util.List;
/**
@@ -65,4 +67,43 @@
/** Policy that controls who should own paths that have no code owners defined. */
public FallbackCodeOwners fallbackCodeOwners;
+
+ /** Emails of users that should be code owners globally across all branches. */
+ public List<String> globalCodeOwners;
+
+ /** Strategy that defines for merge commits which files require code owner approvals. */
+ public MergeCommitStrategy mergeCommitStrategy;
+
+ /** Whether an implicit code owner approval from the last uploader is assumed. */
+ public Boolean implicitApprovals;
+
+ /**
+ * URL for a page that provides project/host-specific information about how to request a code
+ * owner override.
+ */
+ public String overrideInfoUrl;
+
+ /** Whether code owner config files are read-only. */
+ public Boolean readOnly;
+
+ /** Policy for validating code owner config files when a commit is received. */
+ public CodeOwnerConfigValidationPolicy enableValidationOnCommitReceived;
+
+ /** Policy for validating code owner config files when a change is submitted. */
+ public CodeOwnerConfigValidationPolicy enableValidationOnSubmit;
+
+ /**
+ * Whether modifications of code owner config files that newly add non-resolvable code owners
+ * should be rejected on commit received and submit.
+ */
+ public Boolean rejectNonResolvableCodeOwners;
+
+ /**
+ * Whether modifications of code owner config files that newly add non-resolvable imports should
+ * be rejected on commit received an submit.
+ */
+ public Boolean rejectNonResolvableImports;
+
+ /** The maximum number of paths that are included in change messages. */
+ public Integer maxPathsInChangeMessages;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/BUILD b/java/com/google/gerrit/plugins/codeowners/backend/BUILD
index 5aefae5..83671a0 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/BUILD
+++ b/java/com/google/gerrit/plugins/codeowners/backend/BUILD
@@ -7,6 +7,7 @@
visibility = ["//visibility:public"],
deps = PLUGIN_DEPS_NEVERLINK + [
"//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/common",
+ "//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/metrics",
"//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/util",
"//plugins/code-owners/proto:owners_metadata_java_proto",
],
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/ChangedFiles.java b/java/com/google/gerrit/plugins/codeowners/backend/ChangedFiles.java
index 78956a2..1e76b05 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/ChangedFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/ChangedFiles.java
@@ -23,9 +23,11 @@
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.common.ChangedFile;
import com.google.gerrit.plugins.codeowners.common.MergeCommitStrategy;
+import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
@@ -60,15 +62,18 @@
private final GitRepositoryManager repoManager;
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final PatchListCache patchListCache;
+ private final CodeOwnerMetrics codeOwnerMetrics;
@Inject
public ChangedFiles(
GitRepositoryManager repoManager,
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
- PatchListCache patchListCache) {
+ PatchListCache patchListCache,
+ CodeOwnerMetrics codeOwnerMetrics) {
this.repoManager = repoManager;
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
this.patchListCache = patchListCache;
+ this.codeOwnerMetrics = codeOwnerMetrics;
}
/**
@@ -139,12 +144,14 @@
logger.atFine().log(
"computing changed files for revision %s in project %s", revCommit.name(), project);
- if (revCommit.getParentCount() > 1
- && MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION.equals(mergeCommitStrategy)) {
- return computeByComparingAgainstAutoMerge(project, revCommit);
- }
+ try (Timer0.Context ctx = codeOwnerMetrics.computeChangedFiles.start()) {
+ if (revCommit.getParentCount() > 1
+ && MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION.equals(mergeCommitStrategy)) {
+ return computeByComparingAgainstAutoMerge(project, revCommit);
+ }
- return computeByComparingAgainstFirstParent(repoConfig, revWalk, revCommit);
+ return computeByComparingAgainstFirstParent(repoConfig, revWalk, revCommit);
+ }
}
/**
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
index 49eddd7..937a118 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
@@ -33,15 +33,14 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
import com.google.gerrit.plugins.codeowners.common.ChangedFile;
import com.google.gerrit.plugins.codeowners.common.CodeOwnerStatus;
+import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.logging.Metadata;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -91,6 +90,7 @@
private final CodeOwnerConfigHierarchy codeOwnerConfigHierarchy;
private final Provider<CodeOwnerResolver> codeOwnerResolver;
private final ApprovalsUtil approvalsUtil;
+ private final CodeOwnerMetrics codeOwnerMetrics;
@Inject
CodeOwnerApprovalCheck(
@@ -101,7 +101,8 @@
CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory,
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
Provider<CodeOwnerResolver> codeOwnerResolver,
- ApprovalsUtil approvalsUtil) {
+ ApprovalsUtil approvalsUtil,
+ CodeOwnerMetrics codeOwnerMetrics) {
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
@@ -110,6 +111,7 @@
this.codeOwnerConfigHierarchy = codeOwnerConfigHierarchy;
this.codeOwnerResolver = codeOwnerResolver;
this.approvalsUtil = approvalsUtil;
+ this.codeOwnerMetrics = codeOwnerMetrics;
}
/**
@@ -200,13 +202,11 @@
public Stream<FileCodeOwnerStatus> getFileStatuses(ChangeNotes changeNotes)
throws ResourceConflictException, IOException, PatchListNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Compute file statuses",
- Metadata.builder()
- .projectName(changeNotes.getProjectName().get())
- .changeId(changeNotes.getChangeId().get())
- .build())) {
+ try (Timer0.Context ctx = codeOwnerMetrics.computeFileStatuses.start()) {
+ logger.atFine().log(
+ "compute file statuses (project = %s, change = %d)",
+ changeNotes.getProjectName(), changeNotes.getChangeId().get());
+
boolean enableImplicitApprovalFromUploader =
codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(changeNotes.getProjectName());
Account.Id patchSetUploader = changeNotes.getCurrentPatchSet().uploader();
@@ -287,14 +287,14 @@
requireNonNull(changeNotes, "changeNotes");
requireNonNull(patchSet, "patchSet");
requireNonNull(accountId, "accountId");
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Compute file statuses for account",
- Metadata.builder()
- .projectName(changeNotes.getProjectName().get())
- .changeId(changeNotes.getChangeId().get())
- .patchSetId(patchSet.id().get())
- .build())) {
+ try (Timer0.Context ctx = codeOwnerMetrics.computeFileStatusesForAccount.start()) {
+ logger.atFine().log(
+ "compute file statuses for account %d (project = %s, change = %d, patch set = %d)",
+ accountId.get(),
+ changeNotes.getProjectName(),
+ changeNotes.getChangeId().get(),
+ patchSet.id().get());
+
RequiredApproval requiredApproval =
codeOwnersPluginConfiguration.getRequiredApproval(changeNotes.getProjectName());
logger.atFine().log("requiredApproval = %s", requiredApproval);
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
index 2fb7607..31dda71 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
@@ -25,7 +25,9 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
+import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
@@ -33,9 +35,6 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.logging.Metadata;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -62,6 +61,7 @@
private final AccountCache accountCache;
private final AccountControl.Factory accountControlFactory;
private final PathCodeOwners.Factory pathCodeOwnersFactory;
+ private final CodeOwnerMetrics codeOwnerMetrics;
// Enforce visibility by default.
private boolean enforceVisibility = true;
@@ -79,7 +79,8 @@
ExternalIds externalIds,
AccountCache accountCache,
AccountControl.Factory accountControlFactory,
- PathCodeOwners.Factory pathCodeOwnersFactory) {
+ PathCodeOwners.Factory pathCodeOwnersFactory,
+ CodeOwnerMetrics codeOwnerMetrics) {
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
this.permissionBackend = permissionBackend;
this.currentUser = currentUser;
@@ -87,6 +88,7 @@
this.accountCache = accountCache;
this.accountControlFactory = accountControlFactory;
this.pathCodeOwnersFactory = pathCodeOwnersFactory;
+ this.codeOwnerMetrics = codeOwnerMetrics;
}
/**
@@ -147,15 +149,10 @@
requireNonNull(absolutePath, "absolutePath");
checkState(absolutePath.isAbsolute(), "path %s must be absolute", absolutePath);
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Resolve path code owners",
- Metadata.builder()
- .projectName(codeOwnerConfig.key().project().get())
- .branchName(codeOwnerConfig.key().ref())
- .filePath(codeOwnerConfig.key().fileName().orElse("<default>"))
- .build())) {
- logger.atFine().log("resolving path code owners for path %s", absolutePath);
+ try (Timer0.Context ctx = codeOwnerMetrics.resolvePathCodeOwners.start()) {
+ logger.atFine().log(
+ "resolve path code owners (code owner config = %s, path = %s)",
+ codeOwnerConfig.key(), absolutePath);
PathCodeOwnersResult pathCodeOwnersResult =
pathCodeOwnersFactory.create(codeOwnerConfig, absolutePath).resolveCodeOwnerConfig();
return resolve(
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
index 6cf8ef3..4c23676 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
@@ -23,10 +23,9 @@
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
-import com.google.gerrit.server.logging.Metadata;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -56,13 +55,16 @@
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final CodeOwnerApprovalCheck codeOwnerApprovalCheck;
+ private final CodeOwnerMetrics codeOwnerMetrics;
@Inject
CodeOwnerSubmitRule(
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
- CodeOwnerApprovalCheck codeOwnerApprovalCheck) {
+ CodeOwnerApprovalCheck codeOwnerApprovalCheck,
+ CodeOwnerMetrics codeOwnerMetrics) {
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
this.codeOwnerApprovalCheck = codeOwnerApprovalCheck;
+ this.codeOwnerMetrics = codeOwnerMetrics;
}
@Override
@@ -70,13 +72,11 @@
try {
requireNonNull(changeData, "changeData");
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Run code owner submit rule",
- Metadata.builder()
- .projectName(changeData.project().get())
- .changeId(changeData.getId().get())
- .build())) {
+ try (Timer0.Context ctx = codeOwnerMetrics.runCodeOwnerSubmitRule.start()) {
+ logger.atFine().log(
+ "run code owner submit rule (project = %s, change = %d)",
+ changeData.project().get(), changeData.getId().get());
+
if (codeOwnersPluginConfiguration.isDisabled(changeData.change().getDest())) {
logger.atFine().log(
"code owners functionality is disabled for branch %s", changeData.change().getDest());
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index f787dc8..4f67254 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -24,10 +24,9 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
-import com.google.gerrit.server.logging.Metadata;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
@@ -54,15 +53,18 @@
@Singleton
public static class Factory {
+ private final CodeOwnerMetrics codeOwnerMetrics;
private final ProjectCache projectCache;
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final CodeOwners codeOwners;
@Inject
Factory(
+ CodeOwnerMetrics codeOwnerMetrics,
ProjectCache projectCache,
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
CodeOwners codeOwners) {
+ this.codeOwnerMetrics = codeOwnerMetrics;
this.projectCache = projectCache;
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
this.codeOwners = codeOwners;
@@ -71,6 +73,7 @@
public PathCodeOwners create(CodeOwnerConfig codeOwnerConfig, Path absolutePath) {
requireNonNull(codeOwnerConfig, "codeOwnerConfig");
return new PathCodeOwners(
+ codeOwnerMetrics,
projectCache,
codeOwners,
codeOwnerConfig,
@@ -85,6 +88,7 @@
.map(
codeOwnerConfig ->
new PathCodeOwners(
+ codeOwnerMetrics,
projectCache,
codeOwners,
codeOwnerConfig,
@@ -118,6 +122,7 @@
}
}
+ private final CodeOwnerMetrics codeOwnerMetrics;
private final ProjectCache projectCache;
private final CodeOwners codeOwners;
private final CodeOwnerConfig codeOwnerConfig;
@@ -127,11 +132,13 @@
private PathCodeOwnersResult pathCodeOwnersResult;
private PathCodeOwners(
+ CodeOwnerMetrics codeOwnerMetrics,
ProjectCache projectCache,
CodeOwners codeOwners,
CodeOwnerConfig codeOwnerConfig,
Path path,
PathExpressionMatcher pathExpressionMatcher) {
+ this.codeOwnerMetrics = requireNonNull(codeOwnerMetrics, "codeOwnerMetrics");
this.projectCache = requireNonNull(projectCache, "projectCache");
this.codeOwners = requireNonNull(codeOwners, "codeOwners");
this.codeOwnerConfig = requireNonNull(codeOwnerConfig, "codeOwnerConfig");
@@ -187,13 +194,7 @@
return this.pathCodeOwnersResult;
}
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Resolve code owner config",
- Metadata.builder()
- .projectName(codeOwnerConfig.key().project().get())
- .filePath(path.toString())
- .build())) {
+ try (Timer0.Context ctx = codeOwnerMetrics.resolveCodeOwnerConfig.start()) {
logger.atFine().log(
"resolve code owners for %s from code owner config %s", path, codeOwnerConfig.key());
@@ -249,14 +250,9 @@
CodeOwnerConfig importingCodeOwnerConfig,
CodeOwnerConfig.Builder resolvedCodeOwnerConfigBuilder) {
ImmutableList.Builder<UnresolvedImport> unresolvedImports = ImmutableList.builder();
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Resolve code owner config imports",
- Metadata.builder()
- .projectName(codeOwnerConfig.key().project().get())
- .branchName(codeOwnerConfig.key().ref())
- .filePath(codeOwnerConfig.key().filePath("<default>").toString())
- .build())) {
+ try (Timer0.Context ctx = codeOwnerMetrics.resolveCodeOwnerConfigImports.start()) {
+ logger.atFine().log("resolve imports of codeOwnerConfig %s", importingCodeOwnerConfig.key());
+
// To detect cyclic dependencies we keep track of all seen code owner configs.
Set<CodeOwnerConfig.Key> seenCodeOwnerConfigs = new HashSet<>();
seenCodeOwnerConfigs.add(codeOwnerConfig.key());
@@ -278,17 +274,10 @@
CodeOwnerConfig.Key keyOfImportedCodeOwnerConfig =
createKeyForImportedCodeOwnerConfig(
importingCodeOwnerConfig.key(), codeOwnerConfigReference);
- try (TraceTimer traceTimer2 =
- TraceContext.newTimer(
- "Resolve code owner config import",
- Metadata.builder()
- .projectName(keyOfImportedCodeOwnerConfig.project().get())
- .branchName(keyOfImportedCodeOwnerConfig.ref())
- .filePath(
- keyOfImportedCodeOwnerConfig
- .filePath(codeOwnerConfigReference.fileName())
- .toString())
- .build())) {
+ try (Timer0.Context ctx2 = codeOwnerMetrics.resolveCodeOwnerConfigImport.start()) {
+ logger.atFine().log(
+ "resolve import of code owner config %s", keyOfImportedCodeOwnerConfig);
+
Optional<ProjectState> projectState =
projectCache.get(keyOfImportedCodeOwnerConfig.project());
if (!projectState.isPresent()) {
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
index 8348893..3e5538c 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
@@ -79,7 +79,7 @@
@VisibleForTesting public static final String KEY_OVERRIDE_INFO_URL = "overrideInfoUrl";
- @VisibleForTesting static final int DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES = 100;
+ @VisibleForTesting public static final int DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES = 100;
@VisibleForTesting
public static final String KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS =
diff --git a/java/com/google/gerrit/plugins/codeowners/metrics/BUILD b/java/com/google/gerrit/plugins/codeowners/metrics/BUILD
new file mode 100644
index 0000000..1f1ad14
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/metrics/BUILD
@@ -0,0 +1,9 @@
+load("@rules_java//java:defs.bzl", "java_library")
+load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS_NEVERLINK")
+
+java_library(
+ name = "metrics",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+ deps = PLUGIN_DEPS_NEVERLINK,
+)
diff --git a/java/com/google/gerrit/plugins/codeowners/metrics/CodeOwnerMetrics.java b/java/com/google/gerrit/plugins/codeowners/metrics/CodeOwnerMetrics.java
new file mode 100644
index 0000000..e1ce3bf
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/metrics/CodeOwnerMetrics.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.metrics;
+
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/** Metrics of the code-owners plugin. */
+@Singleton
+public class CodeOwnerMetrics {
+ public final Timer0 computeChangedFiles;
+ public final Timer0 computeFileStatuses;
+ public final Timer0 computeFileStatusesForAccount;
+ public final Timer0 resolveCodeOwnerConfig;
+ public final Timer0 resolveCodeOwnerConfigImport;
+ public final Timer0 resolveCodeOwnerConfigImports;
+ public final Timer0 resolvePathCodeOwners;
+ public final Timer0 runCodeOwnerSubmitRule;
+
+ private final MetricMaker metricMaker;
+
+ @Inject
+ CodeOwnerMetrics(MetricMaker metricMaker) {
+ this.metricMaker = metricMaker;
+
+ this.computeChangedFiles =
+ createLatencyTimer("compute_changed_files", "Latency for computing changed files");
+ this.computeFileStatuses =
+ createLatencyTimer("compute_file_statuses", "Latency for computing file statuses");
+ this.computeFileStatusesForAccount =
+ createLatencyTimer(
+ "compute_file_statuses_for_account",
+ "Latency for computing file statuses for an account");
+ this.resolveCodeOwnerConfig =
+ createLatencyTimer(
+ "resolve_code_owner_config", "Latency for resolving a code owner config file");
+ this.resolveCodeOwnerConfigImport =
+ createLatencyTimer(
+ "resolve_code_owner_config_import",
+ "Latency for resolving an import of a code owner config file");
+ this.resolveCodeOwnerConfigImports =
+ createLatencyTimer(
+ "resolve_code_owner_config_imports",
+ "Latency for resolving all imports of a code owner config file");
+ this.resolvePathCodeOwners =
+ createLatencyTimer(
+ "resolve_path_code_owners", "Latency for resolving the code owners of a path");
+ this.runCodeOwnerSubmitRule =
+ createLatencyTimer(
+ "run_code_owner_submit_rule", "Latency for running the code owner submit rule");
+ }
+
+ private Timer0 createLatencyTimer(String name, String description) {
+ return metricMaker.newTimer(
+ "code_owners/" + name,
+ new Description(description).setCumulative().setUnit(Units.MILLISECONDS));
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java b/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
index 69cf3db..597b694 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
@@ -15,8 +15,18 @@
package com.google.gerrit.plugins.codeowners.restapi;
import static com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_SUBMIT;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_FALLBACK_CODE_OWNERS;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_FILE_EXTENSION;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_GLOBAL_CODE_OWNER;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_MAX_PATHS_IN_CHANGE_MESSAGES;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_MERGE_COMMIT_STRATEGY;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_OVERRIDE_INFO_URL;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_READ_ONLY;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS;
import static com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL;
import static com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig.KEY_REQUIRED_APPROVAL;
import static com.google.gerrit.plugins.codeowners.backend.config.StatusConfig.KEY_DISABLED;
@@ -150,6 +160,83 @@
input.fallbackCodeOwners);
}
+ if (input.globalCodeOwners != null) {
+ codeOwnersConfig.setStringList(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_GLOBAL_CODE_OWNER,
+ input.globalCodeOwners);
+ }
+
+ if (input.mergeCommitStrategy != null) {
+ codeOwnersConfig.setEnum(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_MERGE_COMMIT_STRATEGY,
+ input.mergeCommitStrategy);
+ }
+
+ if (input.implicitApprovals != null) {
+ codeOwnersConfig.setBoolean(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_ENABLE_IMPLICIT_APPROVALS,
+ input.implicitApprovals);
+ }
+
+ if (input.overrideInfoUrl != null) {
+ codeOwnersConfig.setString(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_OVERRIDE_INFO_URL,
+ input.overrideInfoUrl);
+ }
+
+ if (input.readOnly != null) {
+ codeOwnersConfig.setBoolean(
+ SECTION_CODE_OWNERS, /* subsection= */ null, KEY_READ_ONLY, input.readOnly);
+ }
+
+ if (input.enableValidationOnCommitReceived != null) {
+ codeOwnersConfig.setEnum(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED,
+ input.enableValidationOnCommitReceived);
+ }
+
+ if (input.enableValidationOnSubmit != null) {
+ codeOwnersConfig.setEnum(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_ENABLE_VALIDATION_ON_SUBMIT,
+ input.enableValidationOnSubmit);
+ }
+
+ if (input.rejectNonResolvableCodeOwners != null) {
+ codeOwnersConfig.setBoolean(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ input.rejectNonResolvableCodeOwners);
+ }
+
+ if (input.rejectNonResolvableImports != null) {
+ codeOwnersConfig.setBoolean(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ input.rejectNonResolvableImports);
+ }
+
+ if (input.maxPathsInChangeMessages != null) {
+ codeOwnersConfig.setInt(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ KEY_MAX_PATHS_IN_CHANGE_MESSAGES,
+ input.maxPathsInChangeMessages);
+ }
+
validateConfig(projectResource.getProjectState(), codeOwnersConfig);
codeOwnersProjectConfigFile.commit(metaDataUpdate);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
index 11d60b7..6ce59bc 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
@@ -14,6 +14,7 @@
package com.google.gerrit.plugins.codeowners.acceptance.api;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
@@ -33,9 +34,13 @@
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInput;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
+import com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig;
import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
+import com.google.gerrit.plugins.codeowners.common.CodeOwnerConfigValidationPolicy;
+import com.google.gerrit.plugins.codeowners.common.MergeCommitStrategy;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.project.DeleteRef;
import com.google.inject.Inject;
@@ -308,6 +313,184 @@
}
@Test
+ public void setGlobalCodeOwners() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.getGlobalCodeOwners(project)).isEmpty();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.globalCodeOwners = ImmutableList.of(user.email(), "foo.bar@example.com");
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(
+ codeOwnersPluginConfiguration.getGlobalCodeOwners(project).stream()
+ .map(CodeOwnerReference::email)
+ .collect(toImmutableSet()))
+ .containsExactly(user.email(), "foo.bar@example.com");
+
+ input.globalCodeOwners = ImmutableList.of();
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getGlobalCodeOwners(project)).isEmpty();
+ }
+
+ @Test
+ public void setMergeCommitStrategy() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
+ .isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.mergeCommitStrategy = MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION;
+ CodeOwnerProjectConfigInfo updatedConfig =
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.mergeCommitStrategy)
+ .isEqualTo(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+ assertThat(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
+ .isEqualTo(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+
+ input.mergeCommitStrategy = MergeCommitStrategy.ALL_CHANGED_FILES;
+ updatedConfig = projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.mergeCommitStrategy)
+ .isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
+ assertThat(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
+ .isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
+ }
+
+ @Test
+ public void setImplicitApprovals() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).isFalse();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.implicitApprovals = true;
+ CodeOwnerProjectConfigInfo updatedConfig =
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.implicitApprovals).isTrue();
+ assertThat(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).isTrue();
+
+ input.implicitApprovals = false;
+ updatedConfig = projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.implicitApprovals).isNull();
+ assertThat(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).isFalse();
+ }
+
+ @Test
+ public void setOverrideInfoUrl() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.getOverrideInfoUrl(project)).isEmpty();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.overrideInfoUrl = "http://foo.bar";
+ CodeOwnerProjectConfigInfo updatedConfig =
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.overrideInfoUrl).isEqualTo("http://foo.bar");
+ assertThat(codeOwnersPluginConfiguration.getOverrideInfoUrl(project))
+ .value()
+ .isEqualTo("http://foo.bar");
+
+ input.overrideInfoUrl = "";
+ updatedConfig = projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(updatedConfig.general.overrideInfoUrl).isNull();
+ assertThat(codeOwnersPluginConfiguration.getOverrideInfoUrl(project)).isEmpty();
+ }
+
+ @Test
+ public void setReadOnly() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.areCodeOwnerConfigsReadOnly(project)).isFalse();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.readOnly = true;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.areCodeOwnerConfigsReadOnly(project)).isTrue();
+
+ input.readOnly = false;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.areCodeOwnerConfigsReadOnly(project)).isFalse();
+ }
+
+ @Test
+ public void setEnableValidationOnCommitReceived() throws Exception {
+ assertThat(
+ codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForCommitReceived(
+ project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.enableValidationOnCommitReceived = CodeOwnerConfigValidationPolicy.FALSE;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(
+ codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForCommitReceived(
+ project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+
+ input.enableValidationOnCommitReceived = CodeOwnerConfigValidationPolicy.TRUE;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(
+ codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForCommitReceived(
+ project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+ }
+
+ @Test
+ public void setEnableValidationOnSubmit() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForSubmit(project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.enableValidationOnSubmit = CodeOwnerConfigValidationPolicy.FALSE;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForSubmit(project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+
+ input.enableValidationOnSubmit = CodeOwnerConfigValidationPolicy.TRUE;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getCodeOwnerConfigValidationPolicyForSubmit(project))
+ .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+ }
+
+ @Test
+ public void setRejectNonResolvableCodeOwners() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableCodeOwners(project)).isTrue();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.rejectNonResolvableCodeOwners = false;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableCodeOwners(project)).isFalse();
+
+ input.rejectNonResolvableCodeOwners = true;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableCodeOwners(project)).isTrue();
+ }
+
+ @Test
+ public void setRejectNonResolvableImports() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableImports(project)).isTrue();
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.rejectNonResolvableImports = false;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableImports(project)).isFalse();
+
+ input.rejectNonResolvableImports = true;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.rejectNonResolvableImports(project)).isTrue();
+ }
+
+ @Test
+ public void setMaxPathsInChangeMessages() throws Exception {
+ assertThat(codeOwnersPluginConfiguration.getMaxPathsInChangeMessages(project))
+ .isEqualTo(GeneralConfig.DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES);
+
+ CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+ input.maxPathsInChangeMessages = 10;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getMaxPathsInChangeMessages(project)).isEqualTo(10);
+
+ input.maxPathsInChangeMessages = 0;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getMaxPathsInChangeMessages(project)).isEqualTo(0);
+
+ input.maxPathsInChangeMessages = GeneralConfig.DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES;
+ projectCodeOwnersApiFactory.project(project).updateConfig(input);
+ assertThat(codeOwnersPluginConfiguration.getMaxPathsInChangeMessages(project))
+ .isEqualTo(GeneralConfig.DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES);
+ }
+
+ @Test
@UseClockStep
public void checkCommitData() throws Exception {
RevCommit head1 = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
diff --git a/resources/Documentation/about.md b/resources/Documentation/about.md
index 6126097..d558de6 100644
--- a/resources/Documentation/about.md
+++ b/resources/Documentation/about.md
@@ -16,3 +16,55 @@
for code owners is pretty generic and [configurable](config.html) so that it
should be suitable for other teams as well.
+## <a id="functionality">Functionality
+
+* Support for defining code owners:
+ * Code owners can be specified in `OWNERS` files that can appear in any
+ directory in the source branch.
+ * Default code owners can be specified on repository level by an `OWNERS`
+ file in the `refs/meta/config` branch.
+ * Global code owners across repositories can be configured.
+ * A fallback code owners policy controls who owns files that are not covered
+ by `OWNERS` files.
+ * Code owners can be specified by email (groups are not supported).
+ * Inheritance from parent directories is supported and can be disabled.
+ * Including an `OWNERS` file from other directories / branches / projects is
+ possible (only on the same host).
+ * File globs can be used.
+ * see [code owners documentation](config-guide.html#codeOwners) and
+ [OWNERS syntax](backend-find-owners.html#syntax)
+<br><br>
+* Prevents submitting changes without code owner approvals:
+ * Which votes count as code owner approvals is
+ [configurable](setup-guide.html#configureCodeOwnerApproval).
+ * Implemented as Java submit rule (no Prolog).
+<br><br>
+* Support for overrides:
+ * Privileged users can be allowed to override the code owner submit check.
+ * Overriding is done by voting on a [configured override
+ label](setup-guide.html#configureCodeOwnerOverrides).
+ * see [override setup](config-faqs.html#setupOverrides)
+<br><br>
+* UI extensions on change screen:
+ * Code owner suggestion
+ * Display of the code owners submit requirement
+ * Display of code owner statuses in the file list
+ * Change messages that list the owned paths.
+ * see [UI walkthrough](how-to-use.html) and [user guide](user-guide.html)
+<br><br>
+* Extensible:
+ * Supports multiple [backends](backends.html) which can implement different
+ syntaxes for `OWNERS` files.
+<br><br>
+* Validation:
+ * updates to `OWNERS` files are [validated](validation.html) on commit
+ received and submit
+ * `OWNERS` files can be validated on demand to detect consistency issues
+<br><br>
+* Rich REST API:
+ * see [REST API documentation](rest-api.html)
+<br><br>
+* Highly configurable:
+ * see [setup guide](setup-guide.html), [config-guide](config-guide.html),
+ [config FAQs](config-faqs.html) and [config documentation](config.html)
+
diff --git a/resources/Documentation/alternative-plugins.md b/resources/Documentation/alternative-plugins.md
index b335414..d3723a1 100644
--- a/resources/Documentation/alternative-plugins.md
+++ b/resources/Documentation/alternative-plugins.md
@@ -1,10 +1,112 @@
# Alternative Plugins
-Similar functionality is provided by the
-[find-owners](https://gerrit-review.googlesource.com/admin/repos/plugins/find-owners)
-plugin and the
-[owners](https://gerrit-review.googlesource.com/admin/repos/plugins/owners)
-plugin.
+Similar functionality is provided by the following plugins:
+
+* [find-owners](#findOwners) plugin
+* [owners](#owners) plugin
+
+## <a id="findOwners">find-owners plugin (deprecated)
+
+**Status:** deprecated, from Gerrit 3.4 on the `code-owners` plugin should be used instead\
+**Repository:** [plugins/find-owners](https://gerrit-review.googlesource.com/admin/repos/plugins/find-owners)\
+**Documentation:** [about](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/about.md), [syntax](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/syntax.md), [REST API](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/rest-api.md), [config](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/config.md)
+
+### <a id="findOwnersCompatibility">Compatibility with the code-owners plugin
+
+The [find-owners](backend-find-owners.html) backend in the`code-owners` plugin
+supports the same syntax for `OWNERS` files as the `find-owners` plugin. This
+means that existing `OWNERS` files continue to work with the `code-owners`
+plugin and no migration for the `OWNERS` files is required.
+
+**IMPORTANT:** When migrating to the `code-owners` plugin, make sure that it is
+correctly configured (see [setup guide](setup-guide.html)).
+
+**NOTE:** The REST API of the `code-owners` plugin is completely different than
+the REST API of the `find-owners` plugin. This means callers of the REST API
+must be adapted to the new API.
+
+**NOTE:** The `OWNERS` syntax in the `code-owners` plugin supports some
+additional features. This means that `OWNERS` files that work with the
+`code-owners` plugin may not work with the `find-owners` plugin.
+
+### <a id="findOwnersFunctionality">Functionality
+
+* Basic support for defining code owners:
+ * Code owners can be specified in `OWNERS` files that can appear in any
+ directory in the source branch.
+ * Code owners can be specified by email.
+ * Inheritance from parent directories is supported and can be disabled.
+ * Including an `OWNERS` file from another directory in the same project or
+ from another project on the same host is possible (same branch is assumed).
+ * File globs can be used.
+ * See [documentation](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/syntax.md) for the supported syntax.
+<br><br>
+* Prolog rule to prevent submitting changes without owner approvals.
+ * A change can be exempted from owners approval by setting a footer in the
+ commit message.
+<br><br>
+* Basic UI:
+ * Supports to discover users that can grant owner approval on a change
+ (weighed suggestion) and add them as reviewer to the change.
+ * Missing owner approvals are visualized on a change.
+ * Owner approval is granted by voting on the `Code-Review` label.
+<br><br>
+* REST endpoints:
+ * [Action](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java) REST endpoint:
+ * `GET /changes/<change-id>/revisions/<revision-id>/find-owners`
+ * returns a [RestResult](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java) which contains:
+ * a file to list of owners map
+ * a list of owner infos with weight infos
+ * fields for debugging
+ * fields for change, patch set, current reviewers and changed files
+ * [GetOwners](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/java/com/googlesource/gerrit/plugins/findowners/GetOwners.java) REST endpoint:
+ * `GET /changes/<change-id>/owners`
+ * Delegates to Action REST endpoint (see above)
+ * Also see [REST endpoint documentation](https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/rest-api.md)
+
+## <a id="owners">owners plugin + owners-autoassign plugin
+
+**Status:** maintained by the Gerrit open source community (no Google involvement)\
+**Repository:** [plugins/owners](https://gerrit-review.googlesource.com/admin/repos/plugins/owners)\
+**Documentation:** [readme](https://gerrit.googlesource.com/plugins/owners/+/master/README.md), [config & syntax](https://gerrit.googlesource.com/plugins/owners/+/master/owners/src/main/resources/Documentation/config.md)
+
+### <a id="ownersCompatibility">Compatibility with the code-owners plugin
+
+The `OWNERS` sytax that is used by the `owners` plugin is **not** compatible
+with the `code-owners` plugin. This means any migration from the `owners` plugin
+to the `code-owners` plugin (and vice versa) requires migrating all existing
+`OWNERS` files.
+
+**NOTE:** It would be feasible to implement a new [backend](backends.html) in
+the `code-owners` plugin that supports the syntax of the `owners` plugin
+(contributions are welcome).
+
+**NOTE:** The `owners` plugin supports groups as code owners, which are not
+supported by the `code-owners` plugin.
+
+### <a id="ownersFunctionality">Functionality
+
+* Basic support for defining code owners in the source branch and globally for
+ a repository:
+ * Code owners can be specified in `OWNERS` files that can appear in any
+ directory in the source branch.
+ * Code owners can be specified on repository level by an `OWNERS` file in
+ the `refs/meta/config` branch.
+ * Code owners can be specified by email or full name.
+ * Groups can be specified as code owners (by group name and group UUID).
+ * Inheritance from parent directories is supported and can be disabled.
+ * Regular expressions can be used.
+ * Syntax is based on YAML.
+ * See [documentation](https://gerrit.googlesource.com/plugins/owners/+/master/owners/src/main/resources/Documentation/config.md) for the supported syntax.
+<br><br>
+* Prolog rule to prevent submitting changes without code owner approvals.
+ * The label on which code owners must vote is configurable.
+<br><br>
+* The UI visualizes whose approval is needed for submit.
+ * Implemented by the `need` description of the Prolog rule, which will be
+ shown in the UI (the plugin doesn't contain any UI code)
+<br><br>
+* Auto-adding of code owners as reviewers.
---
diff --git a/resources/Documentation/config-faqs.md b/resources/Documentation/config-faqs.md
index 4a86c2e..e6971d2 100644
--- a/resources/Documentation/config-faqs.md
+++ b/resources/Documentation/config-faqs.md
@@ -5,6 +5,8 @@
* [How to avoid issues with code owner config files](#avoidIssuesWithCodeOwnerConfigs)
* [How to investigate issues with code owner config files](#investigateIssuesWithCodeOwnerConfigs)
* [How to setup code owner overrides](#setupOverrides)
+* [What's the best place to keep the global plugin
+ configuration](#globalPluginConfiguration)
## <a id="updateCodeOwnersConfig">How to update the code-owners.config file for a project
@@ -132,6 +134,25 @@
Alternatively the permissions can also be assigned via the [Set Access REST
endpoint](../../../Documentation/rest-api-projects.html#set-access).
+## <a id="globalPluginConfiguration">What's the best place to keep the global plugin configuration
+
+The global plugin configuration can either be maintained in the
+[gerrit.config](config.html) file or in the
+[code-owners.config](config.html#projectLevelConfigFile) file in the
+`refs/meta/config` branch of the `All-Projects` project. From the perspective of
+the code-owners plugin both places are equally good. However which place is
+preferred can depend on the system setup, e.g. changes to `gerrit.config` may be
+harder to do and require a multi-day rollout, whereas changes of the
+`All-Projects` configuration can be done through the [REST
+API](rest-api.html#update-code-owner-project-config) and are always instant.
+
+**NOTE:** Any configuration that is done in `All-Projects` overrides the
+corresponding configuration that is inherited from `gerrit.config`.
+
+**NOTE:** There are a few configuration parameters (e.g. for [allowed email
+domains](config.html#pluginCodeOwnersAllowedEmailDomain)) that cannot be set on
+project level and hence must be set in `gerrit.config`.
+
---
Back to [@PLUGIN@ documentation index](index.html)
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 7455898..9c99988 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -146,8 +146,8 @@
<a id="pluginCodeOwnersEnableValidationOnSubmit">plugin.@PLUGIN@.enableValidationOnSubmit</a>
: Policy for validating code owner config files when a change is
submitted. Allowed values are `true` (the code owner config file
- validation is enabled and the submit of invalid code owner config files
- is rejected), `false` (the code owner config file validation is
+ validation is enabled and the submission of invalid code owner config
+ files is rejected), `false` (the code owner config file validation is
disabled, invalid code owner config files are not rejected) and
`dry_run` (code owner config files are validated, but invalid code owner
config files are not rejected).\
diff --git a/resources/Documentation/metrics.md b/resources/Documentation/metrics.md
new file mode 100644
index 0000000..2e63c23
--- /dev/null
+++ b/resources/Documentation/metrics.md
@@ -0,0 +1,27 @@
+# Metrics
+
+The @PLUGIN@ plugin exports several metrics which can give insights into the
+usage and performance of the code owners functionality.
+
+## <a id="latencyMetrics"> Latency Metrics
+
+* `compute_changed_files`:
+ Latency for computing changed files.
+* `compute_file_statuses`:
+ Latency for computing file statuses.
+* `compute_file_statuses_for_account`:
+ Latency for computing file statuses for an account.
+* `resolve_code_owner_config`:
+ Latency for resolving a code owner config file.
+* `resolve_code_owner_config_import`:
+ Latency for resolving an import of a code owner config file.
+* `resolve_code_owner_config_imports`:
+ Latency for resolving all imports of a code owner config file.
+* `run_code_owner_submit_rule`:
+ Latency for running the code owner submit rule.
+
+---
+
+Back to [@PLUGIN@ documentation index](index.html)
+
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 899060d..3fc947a 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -840,6 +840,16 @@
| `required_approval` | optional | The approval that is required from code owners. Must be specified in the format "\<label-name\>+\<label-value\>". If an empty string is provided the required approval configuration is unset. Unsetting the required approval means that the inherited required approval configuration or the default required approval (`Code-Review+1`) will apply. In contrast to providing an empty string, providing `null` (or not setting the value) means that the required approval configuration is not updated.
| `override_approvals` | optional | The approvals that count as override for the code owners submit check. Must be specified in the format "\<label-name\>+\<label-value\>".
| `fallback_code_owners` | optional | Policy that controls who should own paths that have no code owners defined.
+| `global_code_owners` | optional | List of emails of users that should be code owners globally across all branches.
+| `merge_commit_strategy` | optional | Strategy that defines for merge commits which files require code owner approvals. Can be `ALL_CHANGED_FILES` or `FILES_WITH_CONFLICT_RESOLUTION` (see [mergeCommitStrategy](config.html#pluginCodeOwnersMergeCommitStrategy) for an explanation of these values).
+| `implicit_approvals` | optional | Whether an implicit code owner approval from the last uploader is assumed.
+| `override_info_url` | optional | URL for a page that provides project/host-specific information about how to request a code owner override.
+| `read_only` | optional | Whether code owner config files are read-only.
+| `enable_validation_on_commit_received` | optional | Policy for validating code owner config files when a commit is received. Allowed values are `true` (the code owner config file validation is enabled and the upload of invalid code owner config files is rejected), `false` (the code owner config file validation is disabled, invalid code owner config files are not rejected) and `dry_run` (code owner config files are validated, but invalid code owner config files are not rejected).
+| `enable_validation_on_submit` | optional | Policy for validating code owner config files when a change is submitted. Allowed values are `true` (the code owner config file validation is enabled and the submission of invalid code owner config files is rejected), `false` (the code owner config file validation is disabled, invalid code owner config files are not rejected) and `dry_run` (code owner config files are validated, but invalid code owner config files are not rejected).
+| `reject_non_resolvable_code_owners` | optional | Whether modifications of code owner config files that newly add non-resolvable code owners should be rejected on commit received and submit.
+| `reject_non_resolvable_imports` | optional | Whether modifications of code owner config files that newly add non-resolvable imports should be rejected on commit received an submit.
+| `max_paths_in_change_messages` | optional | The maximum number of paths that are included in change messages. Setting the value to `0` disables including owned paths into change messages.
---
diff --git a/resources/Documentation/user-guide.md b/resources/Documentation/user-guide.md
index 70dfaeb..d4308a1 100644
--- a/resources/Documentation/user-guide.md
+++ b/resources/Documentation/user-guide.md
@@ -102,6 +102,14 @@
[ignoreSelfApproval](../../../Documentation/config-labels.html#label_ignoreSelfApproval)
enabled, code owner approvals of the patch set uploader are ignored.
+**NOTE:** Code owner approvals are always applied on the whole change / patch
+set and count for all files in the change / patch set. It is not possible to
+approve individual files only. This means code owners should always review all
+files in the change / patch set before applying their approval. E.g. it is
+discouraged to only review the owned files, since the set of owned files can
+change if `OWNERS` files in the destination branch are changed after the
+approval has been applied.
+
## <a id="codeOwnerOverride">Code owner override
Usually some privileged users, such as sheriffs, are allowed to override the