Merge "Add REST endpoint to get the code owner configuration for a branch"
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
index 8ff1222..1836ca9 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
@@ -25,6 +25,9 @@
* <p>To create an instance for a branch use {@link ProjectCodeOwners#branch(String)}.
*/
public interface BranchCodeOwners {
+ /** Returns the code owner project configuration. */
+ CodeOwnerBranchConfigInfo getConfig() throws RestApiException;
+
/** Create a request to retrieve code owner config files from the branch. */
CodeOwnerConfigFilesRequest codeOwnerConfigFiles() throws RestApiException;
@@ -57,6 +60,11 @@
*/
class NotImplemented implements BranchCodeOwners {
@Override
+ public CodeOwnerBranchConfigInfo getConfig() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public CodeOwnerConfigFilesRequest codeOwnerConfigFiles() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
index 189b0d1..6ed89a9 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
@@ -14,7 +14,10 @@
package com.google.gerrit.plugins.codeowners.api;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig;
import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerConfigFiles;
import com.google.gerrit.server.project.BranchResource;
import com.google.inject.Inject;
@@ -28,18 +31,30 @@
BranchCodeOwnersImpl create(BranchResource branchResource);
}
+ private final GetCodeOwnerBranchConfig getCodeOwnerBranchConfig;
private final Provider<GetCodeOwnerConfigFiles> getCodeOwnerConfigFilesProvider;
private final BranchResource branchResource;
@Inject
public BranchCodeOwnersImpl(
+ GetCodeOwnerBranchConfig getCodeOwnerBranchConfig,
Provider<GetCodeOwnerConfigFiles> getCodeOwnerConfigFilesProvider,
@Assisted BranchResource branchResource) {
this.getCodeOwnerConfigFilesProvider = getCodeOwnerConfigFilesProvider;
+ this.getCodeOwnerBranchConfig = getCodeOwnerBranchConfig;
this.branchResource = branchResource;
}
@Override
+ public CodeOwnerBranchConfigInfo getConfig() throws RestApiException {
+ try {
+ return getCodeOwnerBranchConfig.apply(branchResource).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get code owner branch config", e);
+ }
+ }
+
+ @Override
public CodeOwnerConfigFilesRequest codeOwnerConfigFiles() throws RestApiException {
return new CodeOwnerConfigFilesRequest() {
@Override
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java
new file mode 100644
index 0000000..2cb9375
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.plugins.codeowners.api;
+
+/**
+ * Representation of the code owner branch configuration in the REST API.
+ *
+ * <p>This class determines the JSON format of code owner branch configuration in the REST API.
+ */
+public class CodeOwnerBranchConfigInfo {
+ /**
+ * The general code owners configuration.
+ *
+ * <p>Not set if {@link #disabled} is {@code true}.
+ */
+ public GeneralInfo general;
+
+ /**
+ * Whether the code owners functionality is disabled for the branch.
+ *
+ * <p>If {@code true} the code owners API is disabled and submitting changes doesn't require code
+ * owner approvals.
+ *
+ * <p>Not set if {@code false}.
+ */
+ public Boolean disabled;
+
+ /**
+ * ID of the code owner backend that is configured for the branch.
+ *
+ * <p>Not set if {@link #disabled} is {@code true}.
+ */
+ public String backendId;
+
+ /**
+ * The approval that is required from code owners to approve the files in a change.
+ *
+ * <p>Defines which approval counts as code owner approval.
+ *
+ * <p>Not set if {@link #disabled} is {@code true}.
+ */
+ public RequiredApprovalInfo requiredApproval;
+
+ /**
+ * The approval that is required to override the code owners submit check.
+ *
+ * <p>Not set if {@link #disabled} is {@code true}.
+ */
+ public RequiredApprovalInfo overrideApproval;
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
index b82e25d..737a172 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
@@ -26,6 +26,7 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.plugins.codeowners.api.BackendInfo;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerBranchConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnersStatusInfo;
import com.google.gerrit.plugins.codeowners.api.GeneralInfo;
@@ -34,6 +35,7 @@
import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.config.RequiredApproval;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.BranchResource;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.inject.Inject;
@@ -74,6 +76,26 @@
return info;
}
+ CodeOwnerBranchConfigInfo format(BranchResource branchResource) {
+ CodeOwnerBranchConfigInfo info = new CodeOwnerBranchConfigInfo();
+
+ boolean disabled = codeOwnersPluginConfiguration.isDisabled(branchResource.getBranchKey());
+ info.disabled = disabled ? disabled : null;
+
+ if (disabled) {
+ return info;
+ }
+
+ info.general = formatGeneralInfo(branchResource.getNameKey());
+ info.backendId =
+ CodeOwnerBackendId.getBackendId(
+ codeOwnersPluginConfiguration.getBackend(branchResource.getBranchKey()).getClass());
+ info.requiredApproval = formatRequiredApprovalInfo(branchResource.getNameKey());
+ info.overrideApproval = formatOverrideApprovalInfo(branchResource.getNameKey());
+
+ return info;
+ }
+
private GeneralInfo formatGeneralInfo(Project.NameKey projectName) {
GeneralInfo generalInfo = new GeneralInfo();
generalInfo.fileExtension =
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerBranchConfig.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerBranchConfig.java
new file mode 100644
index 0000000..1aba6a1
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerBranchConfig.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.restapi;
+
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerBranchConfigInfo;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.BranchResource;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+/**
+ * REST endpoint that gets the code owner branch configuration.
+ *
+ * <p>This REST endpoint handles {@code GET
+ * /projects/<project-name>/branches/<branch-name>/code_owners.branch_config} requests.
+ */
+@Singleton
+public class GetCodeOwnerBranchConfig implements RestReadView<BranchResource> {
+ private final CodeOwnerProjectConfigJson codeOwnerProjectConfigJson;
+
+ @Inject
+ public GetCodeOwnerBranchConfig(CodeOwnerProjectConfigJson codeOwnerProjectConfigJson) {
+ this.codeOwnerProjectConfigJson = codeOwnerProjectConfigJson;
+ }
+
+ @Override
+ public Response<CodeOwnerBranchConfigInfo> apply(BranchResource branchResource)
+ throws RestApiException, PermissionBackendException, IOException {
+ branchResource.getProjectState().checkStatePermitsRead();
+
+ return Response.ok(codeOwnerProjectConfigJson.format(branchResource));
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java b/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
index 28e43b0..38fe259 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
@@ -30,6 +30,7 @@
get(CodeOwnerConfigsInBranchCollection.PathResource.PATH_KIND)
.to(GetCodeOwnerConfigForPathInBranch.class);
get(BRANCH_KIND, "code_owners.config_files").to(GetCodeOwnerConfigFiles.class);
+ get(BRANCH_KIND, "code_owners.branch_config").to(GetCodeOwnerBranchConfig.class);
factory(CodeOwnerJson.Factory.class);
DynamicMap.mapOf(binder(), CodeOwnersInBranchCollection.PathResource.PATH_KIND);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
new file mode 100644
index 0000000..20958e3
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
@@ -0,0 +1,288 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.acceptance.api;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerBranchConfigInfo;
+import com.google.gerrit.plugins.codeowners.api.MergeCommitStrategy;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
+import com.google.gerrit.plugins.codeowners.config.BackendConfig;
+import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
+import com.google.gerrit.plugins.codeowners.config.GeneralConfig;
+import com.google.gerrit.plugins.codeowners.config.OverrideApprovalConfig;
+import com.google.gerrit.plugins.codeowners.config.RequiredApprovalConfig;
+import com.google.gerrit.plugins.codeowners.config.StatusConfig;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Acceptance test for the {@link
+ * com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig} REST endpoint.
+ *
+ * <p>Further tests for the {@link
+ * com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig} REST endpoint that require
+ * using the REST API are implemented in {@link
+ * com.google.gerrit.plugins.codeowners.acceptance.restapi.GetCodeOwnerBranchConfigRestIT}.
+ */
+public class GetCodeOwnerBranchConfigIT extends AbstractCodeOwnersIT {
+ private BackendConfig backendConfig;
+
+ @Before
+ public void setup() throws Exception {
+ backendConfig = plugin.getSysInjector().getInstance(BackendConfig.class);
+ }
+
+ @Test
+ public void cannotGetConfigForHiddenProject() throws Exception {
+ ConfigInput configInput = new ConfigInput();
+ configInput.state = ProjectState.HIDDEN;
+ gApi.projects().name(project.get()).config(configInput);
+
+ ResourceConflictException exception =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> projectCodeOwnersApiFactory.project(project).branch("master").getConfig());
+ assertThat(exception).hasMessageThat().isEqualTo("project state HIDDEN does not permit read");
+ }
+
+ @Test
+ public void getDefaultConfig() throws Exception {
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.general.fileExtension).isNull();
+ assertThat(codeOwnerBranchConfigInfo.general.mergeCommitStrategy)
+ .isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
+ assertThat(codeOwnerBranchConfigInfo.general.implicitApprovals).isNull();
+ assertThat(codeOwnerBranchConfigInfo.general.overrideInfoUrl).isNull();
+ assertThat(codeOwnerBranchConfigInfo.disabled).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId)
+ .isEqualTo(CodeOwnerBackendId.getBackendId(backendConfig.getDefaultBackend().getClass()));
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.label)
+ .isEqualTo(RequiredApprovalConfig.DEFAULT_LABEL);
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.value)
+ .isEqualTo(RequiredApprovalConfig.DEFAULT_VALUE);
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
+ @Test
+ public void getConfigWithConfiguredFileExtension() throws Exception {
+ configureFileExtension(project, "foo");
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.general.fileExtension).isEqualTo("foo");
+ }
+
+ @Test
+ public void getConfigWithConfiguredOverrideInfoUrl() throws Exception {
+ configureOverrideInfoUrl(project, "http://foo.example.com");
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.general.overrideInfoUrl)
+ .isEqualTo("http://foo.example.com");
+ }
+
+ @Test
+ public void getConfigWithConfiguredMergeCommitStrategy() throws Exception {
+ configureMergeCommitStrategy(project, MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.general.mergeCommitStrategy)
+ .isEqualTo(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+ }
+
+ @Test
+ public void getConfigForBranchOfDisabledProject() throws Exception {
+ disableCodeOwnersForProject(project);
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.disabled).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.general).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.disabled", value = "true")
+ @GerritConfig(name = "plugin.code-owners.requiredApproval", value = "INVALID")
+ public void getConfigForBranchOfDisabledProject_invalidPluginConfig() throws Exception {
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.disabled).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.general).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
+ @Test
+ public void getConfigForDisabledBranch() throws Exception {
+ configureDisabledBranch(project, "refs/heads/master");
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.disabled).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.general).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/heads/master")
+ @GerritConfig(name = "plugin.code-owners.requiredApproval", value = "INVALID")
+ public void getConfigForDisabledBranch_invalidPluginConfig() throws Exception {
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.disabled).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.general).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
+ @Test
+ public void getConfigWithConfiguredBackend() throws Exception {
+ String otherBackendId = getOtherCodeOwnerBackend(backendConfig.getDefaultBackend());
+ configureBackend(project, otherBackendId);
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isEqualTo(otherBackendId);
+ }
+
+ @Test
+ public void getConfigWithConfiguredBackendForBranch() throws Exception {
+ String otherBackendId = getOtherCodeOwnerBackend(backendConfig.getDefaultBackend());
+ configureBackend(project, "refs/heads/master", otherBackendId);
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isEqualTo(otherBackendId);
+ }
+
+ @Test
+ public void getConfigWithConfiguredRequiredApproval() throws Exception {
+ configureRequiredApproval(project, "Code-Review+2");
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.label).isEqualTo("Code-Review");
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.value).isEqualTo(2);
+ }
+
+ @Test
+ public void getConfigWithConfiguredOverrideApproval() throws Exception {
+ configureOverrideApproval(project, "Code-Review+2");
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval.label).isEqualTo("Code-Review");
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval.value).isEqualTo(2);
+ }
+
+ @Test
+ public void getConfigWithEnabledImplicitApprovals() throws Exception {
+ configureImplicitApprovals(project);
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
+ assertThat(codeOwnerBranchConfigInfo.general.implicitApprovals).isTrue();
+ }
+
+ private void configureFileExtension(Project.NameKey project, String fileExtension)
+ throws Exception {
+ setConfig(project, null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
+ }
+
+ private void configureOverrideInfoUrl(Project.NameKey project, String overrideInfoUrl)
+ throws Exception {
+ setConfig(project, null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
+ }
+
+ private void configureMergeCommitStrategy(
+ Project.NameKey project, MergeCommitStrategy mergeCommitStrategy) throws Exception {
+ setConfig(project, null, GeneralConfig.KEY_MERGE_COMMIT_STRATEGY, mergeCommitStrategy.name());
+ }
+
+ private void configureDisabledBranch(Project.NameKey project, String disabledBranch)
+ throws Exception {
+ setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
+ }
+
+ private void configureBackend(Project.NameKey project, String backendName) throws Exception {
+ configureBackend(project, null, backendName);
+ }
+
+ private void configureBackend(
+ Project.NameKey project, @Nullable String branch, String backendName) throws Exception {
+ setConfig(project, branch, BackendConfig.KEY_BACKEND, backendName);
+ }
+
+ private void configureRequiredApproval(Project.NameKey project, String requiredApproval)
+ throws Exception {
+ setConfig(project, null, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL, requiredApproval);
+ }
+
+ private void configureOverrideApproval(Project.NameKey project, String overrideApproval)
+ throws Exception {
+ setConfig(project, null, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL, overrideApproval);
+ }
+
+ private void configureImplicitApprovals(Project.NameKey project) throws Exception {
+ setConfig(project, null, GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS, "true");
+ }
+
+ private void setConfig(Project.NameKey project, String subsection, String key, String value)
+ throws Exception {
+ Config codeOwnersConfig = new Config();
+ codeOwnersConfig.setString(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, value);
+ try (TestRepository<Repository> testRepo =
+ new TestRepository<>(repoManager.openRepository(project))) {
+ Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
+ RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
+ testRepo.update(
+ RefNames.REFS_CONFIG,
+ testRepo
+ .commit()
+ .parent(head)
+ .message("Configure code owner backend")
+ .add("code-owners.config", codeOwnersConfig.toText()));
+ }
+ projectCache.evict(project);
+ }
+
+ /** Returns the ID of a code owner backend that is not the given backend. */
+ private String getOtherCodeOwnerBackend(CodeOwnerBackend codeOwnerBackend) {
+ for (CodeOwnerBackendId codeOwnerBackendId : CodeOwnerBackendId.values()) {
+ if (!codeOwnerBackendId.getCodeOwnerBackendClass().equals(codeOwnerBackend.getClass())) {
+ return codeOwnerBackendId.getBackendId();
+ }
+ }
+ throw new IllegalStateException(
+ String.format("couldn't find other backend than %s", codeOwnerBackend.getClass()));
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
index 809965d..aa43cc1 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
@@ -51,7 +51,8 @@
private static final ImmutableList<RestCall> BRANCH_ENDPOINTS =
ImmutableList.of(
- RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.config_files"));
+ RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.config_files"),
+ RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.branch_config"));
private static final ImmutableList<RestCall> BRANCH_CODE_OWNER_CONFIGS_ENDPOINTS =
ImmutableList.of(RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.config/%s"));
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerBranchConfigRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerBranchConfigRestIT.java
new file mode 100644
index 0000000..4f64c90
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerBranchConfigRestIT.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.acceptance.restapi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
+import org.junit.Test;
+
+/**
+ * Acceptance test for the {@link
+ * com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig} REST endpoint that require
+ * using REST.
+ *
+ * <p>Acceptance test for the {@link
+ * com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig} REST endpoint that can use
+ * the Java API are implemented in {@link
+ * com.google.gerrit.plugins.codeowners.acceptance.api.GetCodeOwnerBranchConfigIT}.
+ *
+ * <p>The tests in this class do not depend on the used code owner backend, hence we do not need to
+ * extend {@link AbstractCodeOwnersIT}.
+ */
+public class GetCodeOwnerBranchConfigRestIT extends AbstractCodeOwnersTest {
+ @Test
+ @GerritConfig(name = "plugin.code-owners.backend", value = "non-existing-backend")
+ public void cannotGetStatusIfPluginConfigurationIsInvalid() throws Exception {
+ RestResponse r =
+ adminRestSession.get(
+ String.format(
+ "/projects/%s/branches/%s/code_owners.branch_config",
+ IdString.fromDecoded(project.get()), "master"));
+ r.assertConflict();
+ assertThat(r.getEntityContent())
+ .contains(
+ "Invalid configuration of the code-owners plugin. Code owner backend"
+ + " 'non-existing-backend' that is configured in gerrit.config (parameter"
+ + " plugin.code-owners.backend) not found.");
+ }
+
+ @Test
+ public void cannotGetStatusIfPluginConfigurationIsInvalid_defaultRequiredApprovalConfigIsInvalid()
+ throws Exception {
+ // Delete the Code-Review label which is required as code owner approval by default.
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete();
+
+ RestResponse r =
+ adminRestSession.get(
+ String.format(
+ "/projects/%s/branches/%s/code_owners.branch_config",
+ IdString.fromDecoded(project.get()), "master"));
+ r.assertConflict();
+ assertThat(r.getEntityContent())
+ .contains(
+ String.format(
+ "Invalid configuration of the code-owners plugin. The default required approval"
+ + " 'Code-Review+1' that is used for project %s is not valid:"
+ + " Default label Code-Review doesn't exist for project %s."
+ + " Please configure a valid required approval in code-owners.config"
+ + " (parameter codeOwners.requiredApproval).",
+ project.get(), project.get()));
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
index ee7cca1..232576a 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
@@ -25,6 +25,7 @@
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
import com.google.gerrit.plugins.codeowners.api.BackendInfo;
+import com.google.gerrit.plugins.codeowners.api.CodeOwnerBranchConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerProjectConfigInfo;
import com.google.gerrit.plugins.codeowners.api.CodeOwnersStatusInfo;
import com.google.gerrit.plugins.codeowners.api.MergeCommitStrategy;
@@ -35,12 +36,16 @@
import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.config.RequiredApproval;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.project.BranchResource;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
+import java.io.IOException;
import java.util.Optional;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -215,8 +220,67 @@
assertThat(statusInfo.disabledBranches).containsExactly("refs/heads/master");
}
+ @Test
+ public void formatCodeOwnerBranchConfig() throws Exception {
+ createOwnersOverrideLabel();
+
+ when(codeOwnersPluginConfiguration.isDisabled(any(BranchNameKey.class))).thenReturn(false);
+ when(codeOwnersPluginConfiguration.getBackend(BranchNameKey.create(project, "master")))
+ .thenReturn(findOwnersBackend);
+ when(codeOwnersPluginConfiguration.getFileExtension(project)).thenReturn(Optional.of("foo"));
+ when(codeOwnersPluginConfiguration.getOverrideInfoUrl(project))
+ .thenReturn(Optional.of("http://foo.example.com"));
+ when(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
+ .thenReturn(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+ when(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).thenReturn(true);
+ when(codeOwnersPluginConfiguration.getRequiredApproval(project))
+ .thenReturn(RequiredApproval.create(getDefaultCodeReviewLabel(), (short) 2));
+ when(codeOwnersPluginConfiguration.getOverrideApproval(project))
+ .thenReturn(
+ Optional.of(
+ RequiredApproval.create(
+ LabelType.withDefaultValues("Owners-Override"), (short) 1)));
+
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ codeOwnerProjectConfigJson.format(createBranchResource("refs/heads/master"));
+ assertThat(codeOwnerBranchConfigInfo.disabled).isNull();
+ assertThat(codeOwnerBranchConfigInfo.general.fileExtension).isEqualTo("foo");
+ assertThat(codeOwnerBranchConfigInfo.general.overrideInfoUrl)
+ .isEqualTo("http://foo.example.com");
+ assertThat(codeOwnerBranchConfigInfo.general.mergeCommitStrategy)
+ .isEqualTo(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
+ assertThat(codeOwnerBranchConfigInfo.general.implicitApprovals).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.backendId)
+ .isEqualTo(CodeOwnerBackendId.FIND_OWNERS.getBackendId());
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.label).isEqualTo("Code-Review");
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval.value).isEqualTo(2);
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval.label).isEqualTo("Owners-Override");
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval.value).isEqualTo(1);
+ }
+
+ @Test
+ public void formatCodeOwnerBranchConfig_disabled() throws Exception {
+ when(codeOwnersPluginConfiguration.isDisabled(any(BranchNameKey.class))).thenReturn(true);
+
+ CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
+ codeOwnerProjectConfigJson.format(createBranchResource("refs/heads/master"));
+ assertThat(codeOwnerBranchConfigInfo.disabled).isTrue();
+ assertThat(codeOwnerBranchConfigInfo.general).isNull();
+ assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
+ assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
+ assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
+ }
+
private ProjectResource createProjectResource() {
return new ProjectResource(
projectCache.get(project).orElseThrow(illegalState(project)), currentUser);
}
+
+ private BranchResource createBranchResource(String branch) throws IOException {
+ try (Repository repository = repoManager.openRepository(project)) {
+ Ref ref = repository.exactRef(branch);
+ return new BranchResource(
+ projectCache.get(project).orElseThrow(illegalState(project)), currentUser, ref);
+ }
+ }
}
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 73bb403..d3a28e3 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -16,6 +16,11 @@
As a response a [CodeOwnerProjectConfigInfo](#code-owner-project-config-info)
entity is returned that describes the code owner project configuration.
+The response includes the configuration of all branches. If a caller is
+interested in a particular branch only, the [Get Code Owner Branch
+Config](#get-code-owner-branch-config) REST endpoint should be used instead, as
+that REST endpoint is much faster if the project contains many branches.
+
#### Request
```
@@ -125,6 +130,43 @@
## <a id="branch-endpoints">Branch Endpoints
+### <a id="get-code-owner-branch-config">Get Code Owner Branch Config
+_'GET /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/branches/[\{branch-id\}](../../../Documentation/rest-api-projects.html#branch-id)/code_owners.branch_config'_
+
+Gets the code owner branch configuration.
+
+As a response a [CodeOwnerBranchConfigInfo](#code-owner-branch-config-info)
+entity is returned that describes the code owner branch configuration.
+
+#### Request
+
+```
+ GET /projects/foo%2Fbar/branches/master/code_owners.branch_config HTTP/1.0
+```
+
+#### Response
+
+```
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "general": {
+ "merge_commit_strategy": "ALL_CHANGED_FILES"
+ },
+ "backend_id": "find-owners",
+ "required_approval": {
+ "label": "Code-Review",
+ "value": 1
+ },
+ "override_approval": {
+ "label": "Owners-Override",
+ "value": 1
+ }
+ }
+
### <a id="list-code-owner-config-files">List Code Owner Config Files
_'GET /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/branches/[\{branch-id\}](../../../Documentation/rest-api-projects.html#branch-id)/code_owners.config_files/'_
@@ -478,6 +520,20 @@
---
+### <a id="code-owner-branch-config-info"> CodeOwnerBranchConfigInfo
+The `CodeOwnerBranchConfigInfo` entity describes the code owner branch
+configuration.
+
+| Field Name | | Description |
+| ----------- | -------- | ----------- |
+| `general` | optional | The general code owners configuration as [GeneralInfo](#general-info) entity. Not set if `disabled` is `true`.
+| `disabled` | optional | Whether the code owners functionality is disabled for the branch. If `true` the code owners API is disabled and submitting changes doesn't require code owner approvals. Not set if `false`.
+| `backend_id`| optional | ID of the code owner backend that is configured for the branch. Not set if `disabled` is `true`.
+| `required_approval` | optional | The approval that is required from code owners to approve the files in a change as [RequiredApprovalInfo](#required-approval-info) entity. The required approval defines which approval counts as code owner approval. Not set if `disabled` is `true`.
+| `override_approval` | optional | The approval that is required to override the code owners submit check as [RequiredApprovalInfo](#required-approval-info) entity. If unset, overriding the code owners submit check is disabled. Not set if `disabled` is `true`.
+
+---
+
### <a id="code-owner-project-config-info"> CodeOwnerProjectConfigInfo
The `CodeOwnerProjectConfigInfo` entity describes the code owner project
configuration.
diff --git a/resources/Documentation/setup-guide.md b/resources/Documentation/setup-guide.md
index 507f50c..f94044f 100644
--- a/resources/Documentation/setup-guide.md
+++ b/resources/Documentation/setup-guide.md
@@ -378,43 +378,24 @@
##### <a id="checkIfEnabled">How to check if the code owners functionality is enabled for a project or branch
-To check if the code owners functionality is enabled for a project or branch,
-use the [Get Code Owner Project Config](rest-api.html#get-code-owner-project-config)
-REST endpoint and inspect the [status](rest-api.html#code-owners-status-info) in
-the response.
+To check if the code owners functionality is enabled for a single branch, use
+the [Get Code Owner Branch Config](rest-api.html#get-code-owner-branch-config)
+REST endpoint and inspect the
+[disabled](rest-api.html#code-owner-branch-config-info) field in the response
+(if it is not present, the code owners functionality is enabled).
-You can invoke the REST endpoint via `curl` from the command-line or
-alternatively open the following URL in a browser:\
+To check if the code owners functionality is enabled for a project or for
+multiple branches, use the [Get Code Owner Project
+Config](rest-api.html#get-code-owner-project-config) REST endpoint and inspect
+the [status](rest-api.html#code-owners-status-info) in the response (an empty
+status means that the code owners functionality is enabled for all branches of
+the project).
+
+You can invoke the REST endpoints via `curl` from the command-line or
+alternatively open the following URLs in a browser:\
+`https://<host>/projects/<project-name>/branches/<branch-name>/code_owners.branch_config`\
`https://<host>/projects/<project-name>/code_owners.project_config`\
-(remember to URL-encode the project-name)
-
-Example response (an empty status means that the code owners functionality is
-enabled for all branches of the project):
-
-```
- )]}'
- {
- "general": {
- "merge_commit_strategy": "ALL_CHANGED_FILES",
- },
- "status": {
- "disabled_branches": [
- "refs/meta/config"
- ]
- },
- "backend": {
- "id": "find-owners"
- },
- "required_approval": {
- "label": "Code-Review",
- "value": 1
- },
- "override_approval": {
- "label": "Owners-Override",
- "value": 1
- }
- }
-```
+(remember to URL-encode the project-name and branch-name)
---