Add REST endpoint to delete a label definition
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I7b2d642e93298fb5e2aac1d4ac08b7182e1a5149
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index a09075d..67f8592 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3253,6 +3253,37 @@
}
----
+[[delete-label]]
+=== Delete Label
+--
+'DELETE /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
+--
+
+Deletes the definition of a label that is defined in this project.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+The request body does not need to include a link:#delete-label-input[
+DeleteLabelInput] entity if no commit message is specified.
+
+.Request
+----
+ Delete /projects/My-Project/labels/Foo-Review HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "commit_message": "Delete Foo-Review label",
+ }
+----
+
+If a label was deleted the response is "`204 No Content`".
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
[[ids]]
== IDs
@@ -3709,6 +3740,19 @@
Tokens such as `${project}` are not resolved.
|===========================
+[[delete-label-input]]
+=== DeleteLabelInput
+The `DeleteLabelInput` entity contains information for deleting a label
+definition in a project.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`commit_message`|optional|
+Message that should be used to commit the deletion of the label in the
+`project.config` file to the `refs/meta/config` branch.
+|=============================
+
[[delete-branches-input]]
=== DeleteBranchesInput
The `DeleteBranchesInput` entity contains information about branches that should
diff --git a/java/com/google/gerrit/extensions/api/projects/LabelApi.java b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
index bee9e53..975a57e 100644
--- a/java/com/google/gerrit/extensions/api/projects/LabelApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.api.projects;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -26,6 +27,12 @@
LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException;
+ default void delete() throws RestApiException {
+ delete(null);
+ }
+
+ void delete(@Nullable String commitMessage) throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -45,5 +52,10 @@
public LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public void delete(@Nullable String commitMessage) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java b/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java
new file mode 100644
index 0000000..3e96b30
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 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.extensions.common;
+
+import com.google.gerrit.common.Nullable;
+
+/** A generic input with a commit message only. */
+public class InputWithCommitMessage {
+ public String commitMessage;
+
+ public InputWithCommitMessage() {
+ this(null);
+ }
+
+ public InputWithCommitMessage(@Nullable String commitMessage) {
+ this.commitMessage = commitMessage;
+ }
+}
diff --git a/java/com/google/gerrit/server/api/projects/LabelApiImpl.java b/java/com/google/gerrit/server/api/projects/LabelApiImpl.java
index 887f5ae..ad7ec31 100644
--- a/java/com/google/gerrit/server/api/projects/LabelApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/LabelApiImpl.java
@@ -16,7 +16,9 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.projects.LabelApi;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.IdString;
@@ -26,6 +28,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.restapi.project.CreateLabel;
+import com.google.gerrit.server.restapi.project.DeleteLabel;
import com.google.gerrit.server.restapi.project.GetLabel;
import com.google.gerrit.server.restapi.project.LabelsCollection;
import com.google.gerrit.server.restapi.project.SetLabel;
@@ -41,6 +44,7 @@
private final CreateLabel createLabel;
private final GetLabel getLabel;
private final SetLabel setLabel;
+ private final DeleteLabel deleteLabel;
private final ProjectCache projectCache;
private final String label;
@@ -52,6 +56,7 @@
CreateLabel createLabel,
GetLabel getLabel,
SetLabel setLabel,
+ DeleteLabel deleteLabel,
ProjectCache projectCache,
@Assisted ProjectResource project,
@Assisted String label) {
@@ -59,6 +64,7 @@
this.createLabel = createLabel;
this.getLabel = getLabel;
this.setLabel = setLabel;
+ this.deleteLabel = deleteLabel;
this.projectCache = projectCache;
this.project = project;
this.label = label;
@@ -97,6 +103,15 @@
}
}
+ @Override
+ public void delete(@Nullable String commitMessage) throws RestApiException {
+ try {
+ deleteLabel.apply(resource(), new InputWithCommitMessage(commitMessage));
+ } catch (Exception e) {
+ throw asRestApiException("Cannot delete label", e);
+ }
+ }
+
private LabelResource resource() throws RestApiException, PermissionBackendException {
return labels.parse(project, IdString.fromDecoded(label));
}
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
new file mode 100644
index 0000000..5464abf
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2019 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.server.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class DeleteLabel implements RestModifyView<LabelResource, InputWithCommitMessage> {
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final MetaDataUpdate.User updateFactory;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectCache projectCache;
+
+ @Inject
+ public DeleteLabel(
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ MetaDataUpdate.User updateFactory,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectCache projectCache) {
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.updateFactory = updateFactory;
+ this.projectConfigFactory = projectConfigFactory;
+ this.projectCache = projectCache;
+ }
+
+ @Override
+ public Response<?> apply(LabelResource rsrc, InputWithCommitMessage input)
+ throws AuthException, ResourceNotFoundException, PermissionBackendException, IOException,
+ ConfigInvalidException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getProject().getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+
+ if (input == null) {
+ input = new InputWithCommitMessage();
+ }
+
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
+ ProjectConfig config = projectConfigFactory.read(md);
+
+ if (!config.getLabelSections().containsKey(rsrc.getLabelType().getName())) {
+ throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
+ }
+
+ config.getLabelSections().remove(rsrc.getLabelType().getName());
+
+ if (input.commitMessage != null) {
+ md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+ } else {
+ md.setMessage("Delete label");
+ }
+
+ config.commit(md);
+ }
+
+ projectCache.evict(rsrc.getProject().getProjectState().getProject());
+
+ return Response.none();
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index 7ec4e6d..4ed21cc 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -71,6 +71,7 @@
create(LABEL_KIND).to(CreateLabel.class);
get(LABEL_KIND).to(GetLabel.class);
put(LABEL_KIND).to(SetLabel.class);
+ delete(LABEL_KIND).to(DeleteLabel.class);
get(PROJECT_KIND, "HEAD").to(GetHead.class);
put(PROJECT_KIND, "HEAD").to(SetHead.class);
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index ebb918c..02557cc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -168,7 +168,11 @@
*/
private static final ImmutableList<RestCall> LABEL_ENDPOINTS =
ImmutableList.of(
- RestCall.get("/projects/%s/labels/%s"), RestCall.put("/projects/%s/labels/%s"));
+ RestCall.get("/projects/%s/labels/%s"),
+ RestCall.put("/projects/%s/labels/%s"),
+
+ // Label deletion must be tested last
+ RestCall.delete("/projects/%s/labels/%s"));
private static final String FILENAME = "test.txt";
@Inject private ProjectOperations projectOperations;
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java
new file mode 100644
index 0000000..c916285
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java
@@ -0,0 +1,108 @@
+// Copyright (C) 2019 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+public class DeleteLabelIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+ }
+
+ @Test
+ public void nonExisting() throws Exception {
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Non-Existing-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("Not found: Non-Existing-Review");
+ }
+
+ @Test
+ public void delete() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete();
+
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).label("Code-Review").get());
+ assertThat(thrown).hasMessageThat().contains("Not found: Code-Review");
+ }
+
+ @Test
+ public void defaultCommitMessage() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete();
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete label");
+ }
+
+ @Test
+ public void withCommitMessage() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete("Delete Code-Review label");
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete Code-Review label");
+ }
+
+ @Test
+ public void commitMessageIsTrimmed() throws Exception {
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .delete(" Delete Code-Review label ");
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete Code-Review label");
+ }
+}