Add REST endpoint to add new label definitions

Bug: Issue 11522
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I09b8642d266feeeef0c5ae2bdeb106615e4f5bfe
This commit is contained in:
Edwin Kempin
2019-10-25 16:36:45 +02:00
parent b5e8745db4
commit a020d26f4c
10 changed files with 949 additions and 72 deletions

View File

@@ -3139,6 +3139,65 @@ returned that describes the label.
} }
---- ----
[[create-label]]
=== Create Label
--
'PUT /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
--
Creates a new label definition in this project.
The calling user must have write access to the `refs/meta/config` branch of the
project.
If a label with this name is already defined in this project, this label
definition is updated (see link:#set-label[Set Label]).
.Request
----
PUT /projects/My-Project/labels/Foo HTTP/1.0
Content-Type: application/json; charset=UTF-8
{
"commit_message": "Create Foo Label",
"values": {
" 0": "No score",
"-1": "I would prefer this is not merged as is",
"-2": "This shall not be merged",
"+1": "Looks good to me, but someone else must approve",
"+2": "Looks good to me, approved"
}
}
----
As response a link:#label-definition-info[LabelDefinitionInfo] entity is
returned that describes the created label.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
{
"name": "Foo",
"project_name": "My-Project",
"function": "MaxWithBlock",
"values": {
" 0": "No score",
"-1": "I would prefer this is not merged as is",
"-2": "This shall not be merged",
"+1": "Looks good to me, but someone else must approve",
"+2": "Looks good to me, approved"
},
"default_value": 0,
"can_override": true,
"copy_all_scores_if_no_change": true,
"allow_post_submit": true
}
----
[[set-label]] [[set-label]]
=== Set Label === Set Label
-- --

View File

@@ -20,6 +20,8 @@ import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
public interface LabelApi { public interface LabelApi {
LabelApi create(LabelDefinitionInput input) throws RestApiException;
LabelDefinitionInfo get() throws RestApiException; LabelDefinitionInfo get() throws RestApiException;
LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException; LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException;
@@ -29,6 +31,11 @@ public interface LabelApi {
* interface. * interface.
*/ */
class NotImplemented implements LabelApi { class NotImplemented implements LabelApi {
@Override
public LabelApi create(LabelDefinitionInput input) throws RestApiException {
throw new NotImplementedException();
}
@Override @Override
public LabelDefinitionInfo get() throws RestApiException { public LabelDefinitionInfo get() throws RestApiException {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -19,33 +19,70 @@ import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import com.google.gerrit.extensions.api.projects.LabelApi; import com.google.gerrit.extensions.api.projects.LabelApi;
import com.google.gerrit.extensions.common.LabelDefinitionInfo; import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput; import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.LabelResource; import com.google.gerrit.server.project.LabelResource;
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.GetLabel; import com.google.gerrit.server.restapi.project.GetLabel;
import com.google.gerrit.server.restapi.project.LabelsCollection;
import com.google.gerrit.server.restapi.project.SetLabel; import com.google.gerrit.server.restapi.project.SetLabel;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
public class LabelApiImpl implements LabelApi { public class LabelApiImpl implements LabelApi {
interface Factory { interface Factory {
LabelApiImpl create(LabelResource rsrc); LabelApiImpl create(ProjectResource project, String label);
} }
private final LabelsCollection labels;
private final CreateLabel createLabel;
private final GetLabel getLabel; private final GetLabel getLabel;
private final SetLabel setLabel; private final SetLabel setLabel;
private final LabelResource rsrc; private final ProjectCache projectCache;
private final String label;
private ProjectResource project;
@Inject @Inject
LabelApiImpl(GetLabel getLabel, SetLabel setLabel, @Assisted LabelResource rsrc) { LabelApiImpl(
LabelsCollection labels,
CreateLabel createLabel,
GetLabel getLabel,
SetLabel setLabel,
ProjectCache projectCache,
@Assisted ProjectResource project,
@Assisted String label) {
this.labels = labels;
this.createLabel = createLabel;
this.getLabel = getLabel; this.getLabel = getLabel;
this.setLabel = setLabel; this.setLabel = setLabel;
this.rsrc = rsrc; this.projectCache = projectCache;
this.project = project;
this.label = label;
}
@Override
public LabelApi create(LabelDefinitionInput input) throws RestApiException {
try {
createLabel.apply(project, IdString.fromDecoded(label), input);
// recreate project resource because project state was updated by creating the new label and
// needs to be reloaded
project =
new ProjectResource(projectCache.checkedGet(project.getNameKey()), project.getUser());
return this;
} catch (Exception e) {
throw asRestApiException("Cannot create branch", e);
}
} }
@Override @Override
public LabelDefinitionInfo get() throws RestApiException { public LabelDefinitionInfo get() throws RestApiException {
try { try {
return getLabel.apply(rsrc).value(); return getLabel.apply(resource()).value();
} catch (Exception e) { } catch (Exception e) {
throw asRestApiException("Cannot get label", e); throw asRestApiException("Cannot get label", e);
} }
@@ -54,9 +91,13 @@ public class LabelApiImpl implements LabelApi {
@Override @Override
public LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException { public LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException {
try { try {
return setLabel.apply(rsrc, input).value(); return setLabel.apply(resource(), input).value();
} catch (Exception e) { } catch (Exception e) {
throw asRestApiException("Cannot update label", e); throw asRestApiException("Cannot update label", e);
} }
} }
private LabelResource resource() throws RestApiException, PermissionBackendException {
return labels.parse(project, IdString.fromDecoded(label));
}
} }

View File

@@ -73,7 +73,6 @@ import com.google.gerrit.server.restapi.project.GetHead;
import com.google.gerrit.server.restapi.project.GetParent; import com.google.gerrit.server.restapi.project.GetParent;
import com.google.gerrit.server.restapi.project.Index; import com.google.gerrit.server.restapi.project.Index;
import com.google.gerrit.server.restapi.project.IndexChanges; import com.google.gerrit.server.restapi.project.IndexChanges;
import com.google.gerrit.server.restapi.project.LabelsCollection;
import com.google.gerrit.server.restapi.project.ListBranches; import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.gerrit.server.restapi.project.ListDashboards; import com.google.gerrit.server.restapi.project.ListDashboards;
import com.google.gerrit.server.restapi.project.ListLabels; import com.google.gerrit.server.restapi.project.ListLabels;
@@ -132,7 +131,6 @@ public class ProjectApiImpl implements ProjectApi {
private final Index index; private final Index index;
private final IndexChanges indexChanges; private final IndexChanges indexChanges;
private final Provider<ListLabels> listLabels; private final Provider<ListLabels> listLabels;
private final LabelsCollection labels;
private final LabelApiImpl.Factory labelApi; private final LabelApiImpl.Factory labelApi;
@AssistedInject @AssistedInject
@@ -171,7 +169,6 @@ public class ProjectApiImpl implements ProjectApi {
IndexChanges indexChanges, IndexChanges indexChanges,
Provider<ListLabels> listLabels, Provider<ListLabels> listLabels,
LabelApiImpl.Factory labelApi, LabelApiImpl.Factory labelApi,
LabelsCollection labels,
@Assisted ProjectResource project) { @Assisted ProjectResource project) {
this( this(
permissionBackend, permissionBackend,
@@ -209,7 +206,6 @@ public class ProjectApiImpl implements ProjectApi {
indexChanges, indexChanges,
listLabels, listLabels,
labelApi, labelApi,
labels,
null); null);
} }
@@ -249,7 +245,6 @@ public class ProjectApiImpl implements ProjectApi {
IndexChanges indexChanges, IndexChanges indexChanges,
Provider<ListLabels> listLabels, Provider<ListLabels> listLabels,
LabelApiImpl.Factory labelApi, LabelApiImpl.Factory labelApi,
LabelsCollection labels,
@Assisted String name) { @Assisted String name) {
this( this(
permissionBackend, permissionBackend,
@@ -287,7 +282,6 @@ public class ProjectApiImpl implements ProjectApi {
indexChanges, indexChanges,
listLabels, listLabels,
labelApi, labelApi,
labels,
name); name);
} }
@@ -327,7 +321,6 @@ public class ProjectApiImpl implements ProjectApi {
IndexChanges indexChanges, IndexChanges indexChanges,
Provider<ListLabels> listLabels, Provider<ListLabels> listLabels,
LabelApiImpl.Factory labelApi, LabelApiImpl.Factory labelApi,
LabelsCollection labels,
String name) { String name) {
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.createProject = createProject; this.createProject = createProject;
@@ -365,7 +358,6 @@ public class ProjectApiImpl implements ProjectApi {
this.indexChanges = indexChanges; this.indexChanges = indexChanges;
this.listLabels = listLabels; this.listLabels = listLabels;
this.labelApi = labelApi; this.labelApi = labelApi;
this.labels = labels;
} }
@Override @Override
@@ -715,7 +707,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override @Override
public LabelApi label(String labelName) throws RestApiException { public LabelApi label(String labelName) throws RestApiException {
try { try {
return labelApi.create(labels.parse(checkExists(), IdString.fromDecoded(labelName))); return labelApi.create(checkExists(), labelName);
} catch (Exception e) { } catch (Exception e) {
throw asRestApiException("Cannot parse label", e); throw asRestApiException("Cannot parse label", e);
} }

View File

@@ -0,0 +1,172 @@
// 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.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
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.LabelDefinitionJson;
import com.google.gerrit.server.project.LabelResource;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
public class CreateLabel
implements RestCollectionCreateView<ProjectResource, LabelResource, LabelDefinitionInput> {
private final PermissionBackend permissionBackend;
private final MetaDataUpdate.User updateFactory;
private final ProjectConfig.Factory projectConfigFactory;
private final ProjectCache projectCache;
@Inject
public CreateLabel(
PermissionBackend permissionBackend,
MetaDataUpdate.User updateFactory,
ProjectConfig.Factory projectConfigFactory,
ProjectCache projectCache) {
this.permissionBackend = permissionBackend;
this.updateFactory = updateFactory;
this.projectConfigFactory = projectConfigFactory;
this.projectCache = projectCache;
}
@Override
public Response<LabelDefinitionInfo> apply(
ProjectResource rsrc, IdString id, LabelDefinitionInput input)
throws AuthException, BadRequestException, ResourceConflictException,
PermissionBackendException, IOException, ConfigInvalidException {
permissionBackend
.currentUser()
.project(rsrc.getNameKey())
.check(ProjectPermission.WRITE_CONFIG);
if (input == null) {
input = new LabelDefinitionInput();
}
if (input.name != null && !input.name.equals(id.get())) {
throw new BadRequestException("name in input must match name in URL");
}
try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
ProjectConfig config = projectConfigFactory.read(md);
if (config.getLabelSections().containsKey(id.get())) {
throw new ResourceConflictException("label " + id.get() + " already exists");
}
if (input.values == null || input.values.isEmpty()) {
throw new BadRequestException("values are required");
}
List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
LabelType labelType;
try {
labelType = new LabelType(id.get(), values);
} catch (IllegalArgumentException e) {
throw new BadRequestException("invalid name: " + id.get(), e);
}
if (input.function != null && !input.function.trim().isEmpty()) {
labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
} else {
labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
}
if (input.defaultValue != null) {
labelType.setDefaultValue(
LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
}
if (input.branches != null) {
labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
}
if (input.canOverride != null) {
labelType.setCanOverride(input.canOverride);
}
if (input.copyAnyScore != null) {
labelType.setCopyAnyScore(input.copyAnyScore);
}
if (input.copyMinScore != null) {
labelType.setCopyMinScore(input.copyMinScore);
}
if (input.copyMaxScore != null) {
labelType.setCopyMaxScore(input.copyMaxScore);
}
if (input.copyAllScoresIfNoChange != null) {
labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
}
if (input.copyAllScoresIfNoCodeChange != null) {
labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
}
if (input.copyAllScoresOnTrivialRebase != null) {
labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
}
if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
labelType.setCopyAllScoresOnMergeFirstParentUpdate(
input.copyAllScoresOnMergeFirstParentUpdate);
}
if (input.allowPostSubmit != null) {
labelType.setAllowPostSubmit(input.allowPostSubmit);
}
if (input.ignoreSelfApproval != null) {
labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
}
if (input.commitMessage != null) {
md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
} else {
md.setMessage("Update label");
}
config.getLabelSections().put(labelType.getName(), labelType);
config.commit(md);
projectCache.evict(rsrc.getProjectState().getProject());
return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
}
}
}

View File

@@ -0,0 +1,87 @@
// 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.primitives.Shorts;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.project.RefPattern;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
public class LabelDefinitionInputParser {
public static LabelFunction parseFunction(String functionString) throws BadRequestException {
Optional<LabelFunction> function = LabelFunction.parse(functionString.trim());
return function.orElseThrow(
() -> new BadRequestException("unknown function: " + functionString));
}
public static List<LabelValue> parseValues(Map<String, String> values)
throws BadRequestException {
List<LabelValue> valueList = new ArrayList<>();
for (Entry<String, String> e : values.entrySet()) {
short value;
try {
value = Shorts.checkedCast(PermissionRule.parseInt(e.getKey().trim()));
} catch (NumberFormatException ex) {
throw new BadRequestException("invalid value: " + e.getKey(), ex);
}
String valueDescription = e.getValue().trim();
if (valueDescription.isEmpty()) {
throw new BadRequestException("description for value '" + e.getKey() + "' cannot be empty");
}
valueList.add(new LabelValue(value, valueDescription));
}
return valueList;
}
public static short parseDefaultValue(LabelType labelType, short defaultValue)
throws BadRequestException {
if (labelType.getValue(defaultValue) == null) {
throw new BadRequestException("invalid default value: " + defaultValue);
}
return defaultValue;
}
public static List<String> parseBranches(List<String> branches) throws BadRequestException {
List<String> validBranches = new ArrayList<>();
for (String branch : branches) {
String newBranch = branch.trim();
if (newBranch.isEmpty()) {
continue;
}
if (!RefPattern.isRE(newBranch) && !newBranch.startsWith(RefNames.REFS)) {
newBranch = RefNames.REFS_HEADS + newBranch;
}
try {
RefPattern.validate(newBranch);
} catch (InvalidNameException e) {
throw new BadRequestException("invalid branch: " + branch, e);
}
validBranches.add(newBranch);
}
return validBranches;
}
private LabelDefinitionInputParser() {}
}

View File

@@ -68,6 +68,7 @@ public class Module extends RestApiModule {
get(CHILD_PROJECT_KIND).to(GetChildProject.class); get(CHILD_PROJECT_KIND).to(GetChildProject.class);
child(PROJECT_KIND, "labels").to(LabelsCollection.class); child(PROJECT_KIND, "labels").to(LabelsCollection.class);
create(LABEL_KIND).to(CreateLabel.class);
get(LABEL_KIND).to(GetLabel.class); get(LABEL_KIND).to(GetLabel.class);
put(LABEL_KIND).to(SetLabel.class); put(LABEL_KIND).to(SetLabel.class);

View File

@@ -15,13 +15,7 @@
package com.google.gerrit.server.restapi.project; package com.google.gerrit.server.restapi.project;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.primitives.Shorts;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.common.LabelDefinitionInfo; import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput; import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
@@ -37,14 +31,9 @@ import com.google.gerrit.server.project.LabelDefinitionJson;
import com.google.gerrit.server.project.LabelResource; import com.google.gerrit.server.project.LabelResource;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig; import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.RefPattern;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton @Singleton
@@ -106,15 +95,10 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn
} }
if (input.function != null) { if (input.function != null) {
String newFunctionName = input.function.trim(); if (input.function.trim().isEmpty()) {
if (newFunctionName.isEmpty()) {
throw new BadRequestException("function cannot be empty"); throw new BadRequestException("function cannot be empty");
} }
Optional<LabelFunction> newFunction = LabelFunction.parse(newFunctionName); labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
if (!newFunction.isPresent()) {
throw new BadRequestException("unknown function: " + input.function);
}
labelType.setFunction(newFunction.get());
dirty = true; dirty = true;
} }
@@ -122,52 +106,18 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn
if (input.values.isEmpty()) { if (input.values.isEmpty()) {
throw new BadRequestException("values cannot be empty"); throw new BadRequestException("values cannot be empty");
} }
labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
List<LabelValue> newValues = new ArrayList<>();
for (Entry<String, String> e : input.values.entrySet()) {
short value;
try {
value = Shorts.checkedCast(PermissionRule.parseInt(e.getKey().trim()));
} catch (NumberFormatException ex) {
throw new BadRequestException("invalid value: " + e.getKey(), ex);
}
String valueDescription = e.getValue().trim();
if (valueDescription.isEmpty()) {
throw new BadRequestException(
"description for value '" + e.getKey() + "' cannot be empty");
}
newValues.add(new LabelValue(value, valueDescription));
}
labelType.setValues(newValues);
dirty = true; dirty = true;
} }
if (input.defaultValue != null) { if (input.defaultValue != null) {
if (labelType.getValue(input.defaultValue) == null) { labelType.setDefaultValue(
throw new BadRequestException("invalid default value: " + input.defaultValue); LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
}
labelType.setDefaultValue(input.defaultValue);
dirty = true; dirty = true;
} }
if (input.branches != null) { if (input.branches != null) {
List<String> newBranches = new ArrayList<>(); labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
for (String branch : input.branches) {
String newBranch = branch.trim();
if (newBranch.isEmpty()) {
continue;
}
if (!RefPattern.isRE(newBranch) && !newBranch.startsWith(RefNames.REFS)) {
newBranch = RefNames.REFS_HEADS + newBranch;
}
try {
RefPattern.validate(newBranch);
} catch (InvalidNameException e) {
throw new BadRequestException("invalid branch: " + branch, e);
}
newBranches.add(newBranch);
}
labelType.setRefPatterns(newBranches);
dirty = true; dirty = true;
} }

View File

@@ -83,7 +83,8 @@ public class ProjectsRestApiBindingsIT extends AbstractDaemonTest {
// GET /projects/<project>/branches/<branch>/commits is not implemented // GET /projects/<project>/branches/<branch>/commits is not implemented
.expectedResponseCode(SC_NOT_FOUND) .expectedResponseCode(SC_NOT_FOUND)
.build(), .build(),
RestCall.get("/projects/%s/dashboards")); RestCall.get("/projects/%s/dashboards"),
RestCall.put("/projects/%s/labels/new-label"));
/** /**
* Child project REST endpoints to be tested, each URL contains placeholders for the parent * Child project REST endpoints to be tested, each URL contains placeholders for the parent

View File

@@ -0,0 +1,567 @@
// 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.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.inject.Inject;
import org.junit.Test;
@NoHttpd
public class CreateLabelIT extends AbstractDaemonTest {
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private ProjectOperations projectOperations;
@Test
public void notAllowed() throws Exception {
projectOperations
.project(project)
.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(project.get())
.label("Foo-Review")
.create(new LabelDefinitionInput()));
assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
}
@Test
public void cannotCreateLabelIfNameDoesntMatch() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.name = "Foo";
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Bar").create(input));
assertThat(thrown).hasMessageThat().contains("name in input must match name in URL");
}
@Test
public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
ResourceConflictException thrown =
assertThrows(
ResourceConflictException.class,
() ->
gApi.projects()
.name(allProjects.get())
.label("Code-Review")
.create(new LabelDefinitionInput()));
assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
}
@Test
public void cannotCreateLabelWithInvalidName() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("INVALID_NAME").create(input));
assertThat(thrown).hasMessageThat().contains("invalid name: INVALID_NAME");
}
@Test
public void cannotCreateLabelWithoutValues() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("values are required");
input.values = ImmutableMap.of();
thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("values are required");
}
@Test
public void cannotCreateLabelWithInvalidValues() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("invalidValue", "description");
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("invalid value: invalidValue");
}
@Test
public void cannotCreateLabelWithValuesThatHaveEmptyDescription() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "");
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("description for value '+1' cannot be empty");
}
@Test
public void cannotCreateLabelWithInvalidDefaultValue() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
input.defaultValue = 5;
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("invalid default value: " + input.defaultValue);
}
@Test
public void cannotCreateLabelWithUnknownFunction() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
input.function = "UnknownFuction";
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("unknown function: " + input.function);
}
@Test
public void cannotCreateLabelWithInvalidBranch() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
input.branches = ImmutableList.of("refs heads master");
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).label("Foo").create(input));
assertThat(thrown).hasMessageThat().contains("invalid branch: refs heads master");
}
@Test
public void createWithNameAndValuesOnly() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.name).isEqualTo("Foo");
assertThat(createdLabel.projectName).isEqualTo(project.get());
assertThat(createdLabel.function).isEqualTo(LabelFunction.MAX_WITH_BLOCK.getFunctionName());
assertThat(createdLabel.values).containsExactlyEntriesIn(input.values);
assertThat(createdLabel.defaultValue).isEqualTo(0);
assertThat(createdLabel.branches).isNull();
assertThat(createdLabel.canOverride).isTrue();
assertThat(createdLabel.copyAnyScore).isNull();
assertThat(createdLabel.copyMinScore).isNull();
assertThat(createdLabel.copyMaxScore).isNull();
assertThat(createdLabel.copyAllScoresIfNoChange).isTrue();
assertThat(createdLabel.copyAllScoresIfNoCodeChange).isNull();
assertThat(createdLabel.copyAllScoresOnTrivialRebase).isNull();
assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
assertThat(createdLabel.allowPostSubmit).isTrue();
assertThat(createdLabel.ignoreSelfApproval).isNull();
}
@Test
public void createWithFunction() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.function = LabelFunction.NO_OP.getFunctionName();
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.function).isEqualTo(LabelFunction.NO_OP.getFunctionName());
}
@Test
public void functionEmptyAfterTrim() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.function = " ";
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.function).isEqualTo(LabelFunction.MAX_WITH_BLOCK.getFunctionName());
}
@Test
public void valuesAndDescriptionsAreTrimmed() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
// Positive values can be specified as '<value>' or '+<value>'.
input.values =
ImmutableMap.of(
" 2 ",
" Looks Very Good ",
" +1 ",
" Looks Good ",
" 0 ",
" Don't Know ",
" -1 ",
" Looks Bad ",
" -2 ",
" Looks Very Bad ");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.values)
.containsExactly(
"+2", "Looks Very Good",
"+1", "Looks Good",
" 0", "Don't Know",
"-1", "Looks Bad",
"-2", "Looks Very Bad");
}
@Test
public void createWithDefaultValue() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.defaultValue = 1;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.defaultValue).isEqualTo(input.defaultValue);
}
@Test
public void createWithBranches() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
// Branches can be full ref, ref pattern or regular expression.
input.branches =
ImmutableList.of("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.branches).containsExactlyElementsIn(input.branches);
}
@Test
public void branchesAreTrimmed() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.branches =
ImmutableList.of(" refs/heads/master ", " refs/heads/foo/* ", " ^refs/heads/stable-.* ");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.branches)
.containsExactly("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
}
@Test
public void emptyBranchesAreIgnored() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.branches = ImmutableList.of("refs/heads/master", "", " ");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.branches).containsExactly("refs/heads/master");
}
@Test
public void branchesAreAutomaticallyPrefixedWithRefsHeads() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.branches = ImmutableList.of("master", "refs/meta/config");
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("Foo").create(input).get();
assertThat(createdLabel.branches).containsExactly("refs/heads/master", "refs/meta/config");
}
@Test
public void createWithCanOverride() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.canOverride = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.canOverride).isTrue();
}
@Test
public void createWithoutCanOverride() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.canOverride = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.canOverride).isNull();
}
@Test
public void createWithCopyAnyScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAnyScore = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAnyScore).isTrue();
}
@Test
public void createWithoutCopyAnyScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAnyScore = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAnyScore).isNull();
}
@Test
public void createWithCopyMinScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyMinScore = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyMinScore).isTrue();
}
@Test
public void createWithoutCopyMinScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyMinScore = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyMinScore).isNull();
}
@Test
public void createWithCopyMaxScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyMaxScore = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyMaxScore).isTrue();
}
@Test
public void createWithoutCopyMaxScore() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyMaxScore = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyMaxScore).isNull();
}
@Test
public void createWithCopyAllScoresIfNoChange() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresIfNoChange = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresIfNoChange).isTrue();
}
@Test
public void createWithoutCopyAllScoresIfNoChange() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresIfNoChange = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresIfNoChange).isNull();
}
@Test
public void createWithCopyAllScoresIfNoCodeChange() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresIfNoCodeChange = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresIfNoCodeChange).isTrue();
}
@Test
public void createWithoutCopyAllScoresIfNoCodeChange() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresIfNoCodeChange = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresIfNoCodeChange).isNull();
}
@Test
public void createWithCopyAllScoresOnTrivialRebase() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresOnTrivialRebase = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresOnTrivialRebase).isTrue();
}
@Test
public void createWithoutCopyAllScoresOnTrivialRebase() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresOnTrivialRebase = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresOnTrivialRebase).isNull();
}
@Test
public void createWithCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresOnMergeFirstParentUpdate = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
}
@Test
public void createWithoutCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.copyAllScoresOnMergeFirstParentUpdate = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
}
@Test
public void createWithAllowPostSubmit() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.allowPostSubmit = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.allowPostSubmit).isTrue();
}
@Test
public void createWithoutAllowPostSubmit() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.allowPostSubmit = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.allowPostSubmit).isNull();
}
@Test
public void createWithIgnoreSelfApproval() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.ignoreSelfApproval = true;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.ignoreSelfApproval).isTrue();
}
@Test
public void createWithoutIgnoreSelfApproval() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.ignoreSelfApproval = false;
LabelDefinitionInfo createdLabel =
gApi.projects().name(project.get()).label("foo").create(input).get();
assertThat(createdLabel.ignoreSelfApproval).isNull();
}
@Test
public void defaultCommitMessage() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
gApi.projects().name(project.get()).label("Foo").create(input);
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
.isEqualTo("Update label");
}
@Test
public void withCommitMessage() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.commitMessage = "Add Foo Label";
gApi.projects().name(project.get()).label("Foo").create(input);
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
.isEqualTo(input.commitMessage);
}
@Test
public void commitMessageIsTrimmed() throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.commitMessage = " Add Foo Label ";
gApi.projects().name(project.get()).label("Foo").create(input);
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
.isEqualTo("Add Foo Label");
}
}