Merge "gr-main-header: Fix hamburger colour when opened"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 50885b6..dd2c3de 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7224,12 +7224,10 @@
Number of inserted lines.
|`deletions` ||
Number of deleted lines.
-|`total_comment_count` |optional|
-Total number of inline comments across all patch sets. Not set if the current
-change index doesn't have the data.
-|`unresolved_comment_count` |optional|
-Number of unresolved inline comment threads across all patch sets. Not set if
-the current change index doesn't have the data.
+|`total_comment_count` ||
+Total number of inline comments across all patch sets.
+|`unresolved_comment_count` ||
+Number of unresolved inline comment threads across all patch sets.
|`_number` ||
The change number. (The underscore is just a relict of a prior
attempt to deprecate the change number.)
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 96a6d32..cd805d0 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1550,6 +1550,17 @@
assertThat(res).isEqualTo(expectedContent);
}
+ protected void assertLastCommitAuthorAndShortMessage(
+ String refName, String expectedAuthor, String expectedShortMessage) throws Exception {
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ Ref exactRef = repo.exactRef(refName);
+ RevCommit revCommit = rw.parseCommit(exactRef.getObjectId());
+ assertThat(revCommit.getAuthorIdent().getName()).isEqualTo(expectedAuthor);
+ assertThat(revCommit.getShortMessage()).isEqualTo(expectedShortMessage);
+ }
+ }
+
@CanIgnoreReturnValue
protected RevCommit createNewCommitWithoutChangeId(String branch, String file, String content)
throws Exception {
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index 3e8002b..3a50275 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -14,10 +14,8 @@
package com.google.gerrit.server.restapi.project;
-import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.AccessSection;
-import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
@@ -27,9 +25,10 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.restapi.project.RepoMetaDataUpdater.ConfigChangeCreator;
import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -39,23 +38,18 @@
@Singleton
public class CreateAccessChange implements RestModifyView<ProjectResource, ProjectAccessInput> {
private final SetAccessUtil setAccess;
- private final ChangeJson.Factory jsonFactory;
private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
- CreateAccessChange(
- SetAccessUtil accessUtil,
- ChangeJson.Factory jsonFactory,
- RepoMetaDataUpdater repoMetaDataUpdater) {
+ CreateAccessChange(SetAccessUtil accessUtil, RepoMetaDataUpdater repoMetaDataUpdater) {
this.setAccess = accessUtil;
- this.jsonFactory = jsonFactory;
this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
public Response<ChangeInfo> apply(ProjectResource rsrc, ProjectAccessInput input)
- throws PermissionBackendException, IOException, ConfigInvalidException, InvalidNameException,
- UpdateException, RestApiException {
+ throws PermissionBackendException, IOException, ConfigInvalidException, UpdateException,
+ RestApiException {
ImmutableList<AccessSection> removals =
setAccess.getAccessSections(input.remove, /* rejectNonResolvableGroups= */ false);
ImmutableList<AccessSection> additions =
@@ -63,28 +57,23 @@
Project.NameKey newParentProjectName =
input.parent == null ? null : Project.nameKey(input.parent);
- String message = !Strings.isNullOrEmpty(input.message) ? input.message : "Review access change";
- try {
- Change change =
- repoMetaDataUpdater.updateAndCreateChangeForReview(
- rsrc.getNameKey(),
- rsrc.getUser(),
- message,
- config -> {
- setAccess.validateChanges(config, removals, additions);
- setAccess.applyChanges(config, removals, additions);
- try {
- setAccess.setParentName(
- rsrc.getUser().asIdentifiedUser(),
- config,
- rsrc.getNameKey(),
- newParentProjectName,
- false);
- } catch (AuthException e) {
- throw new IllegalStateException(e);
- }
- });
- return Response.created(jsonFactory.noOptions().format(change));
+ try (ConfigChangeCreator creator =
+ repoMetaDataUpdater.configChangeCreator(
+ rsrc.getNameKey(), input.message, "Review access change")) {
+ ProjectConfig config = creator.getConfig();
+ setAccess.validateChanges(config, removals, additions);
+ setAccess.applyChanges(config, removals, additions);
+ try {
+ setAccess.setParentName(
+ rsrc.getUser().asIdentifiedUser(),
+ config,
+ rsrc.getNameKey(),
+ newParentProjectName,
+ false);
+ } catch (AuthException e) {
+ throw new IllegalStateException(e);
+ }
+ return creator.createChange();
} catch (InvalidNameException e) {
throw new BadRequestException(e.toString());
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index a233834..12d1189 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -27,19 +27,13 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.index.query.QueryParseException;
-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.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.gerrit.server.query.approval.ApprovalQueryBuilder;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.List;
@@ -49,27 +43,14 @@
@Singleton
public class CreateLabel
implements RestCollectionCreateView<ProjectResource, LabelResource, LabelDefinitionInput> {
- private final Provider<CurrentUser> user;
- private final PermissionBackend permissionBackend;
- private final MetaDataUpdate.User updateFactory;
- private final ProjectConfig.Factory projectConfigFactory;
- private final ProjectCache projectCache;
private final ApprovalQueryBuilder approvalQueryBuilder;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
public CreateLabel(
- Provider<CurrentUser> user,
- PermissionBackend permissionBackend,
- MetaDataUpdate.User updateFactory,
- ProjectConfig.Factory projectConfigFactory,
- ProjectCache projectCache,
- ApprovalQueryBuilder approvalQueryBuilder) {
- this.user = user;
- this.permissionBackend = permissionBackend;
- this.updateFactory = updateFactory;
- this.projectConfigFactory = projectConfigFactory;
- this.projectCache = projectCache;
+ ApprovalQueryBuilder approvalQueryBuilder, RepoMetaDataUpdater repoMetaDataUpdater) {
this.approvalQueryBuilder = approvalQueryBuilder;
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
@@ -77,15 +58,6 @@
ProjectResource rsrc, IdString id, LabelDefinitionInput input)
throws AuthException, BadRequestException, ResourceConflictException,
PermissionBackendException, IOException, ConfigInvalidException {
- if (!user.get().isIdentifiedUser()) {
- throw new AuthException("Authentication required");
- }
-
- permissionBackend
- .currentUser()
- .project(rsrc.getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
if (input == null) {
input = new LabelDefinitionInput();
}
@@ -93,22 +65,10 @@
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);
-
- LabelType labelType = createLabel(config, id.get(), input);
-
- if (input.commitMessage != null) {
- md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
- } else {
- md.setMessage("Update label");
- }
-
- config.commit(md);
-
- projectCache.evictAndReindex(rsrc.getProjectState().getProject());
-
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(rsrc.getNameKey(), input.commitMessage, "Update label")) {
+ LabelType labelType = createLabel(configUpdater.getConfig(), id.get(), input);
+ configUpdater.commitConfigUpdate();
return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateSubmitRequirement.java b/java/com/google/gerrit/server/restapi/project/CreateSubmitRequirement.java
index a46211c..b946958 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateSubmitRequirement.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateSubmitRequirement.java
@@ -26,12 +26,7 @@
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.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.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.SubmitRequirementExpressionsValidator;
@@ -39,7 +34,6 @@
import com.google.gerrit.server.project.SubmitRequirementResource;
import com.google.gerrit.server.project.SubmitRequirementsUtil;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Optional;
@@ -50,42 +44,42 @@
public class CreateSubmitRequirement
implements RestCollectionCreateView<
ProjectResource, SubmitRequirementResource, SubmitRequirementInput> {
- private final Provider<CurrentUser> user;
- private final PermissionBackend permissionBackend;
- private final MetaDataUpdate.User updateFactory;
- private final ProjectConfig.Factory projectConfigFactory;
- private final ProjectCache projectCache;
private final SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator;
+ private final RepoMetaDataUpdater updater;
@Inject
public CreateSubmitRequirement(
- Provider<CurrentUser> user,
- PermissionBackend permissionBackend,
- MetaDataUpdate.User updateFactory,
- ProjectConfig.Factory projectConfigFactory,
- ProjectCache projectCache,
- SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator) {
- this.user = user;
- this.permissionBackend = permissionBackend;
- this.updateFactory = updateFactory;
- this.projectConfigFactory = projectConfigFactory;
- this.projectCache = projectCache;
+ SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator,
+ RepoMetaDataUpdater updater) {
this.submitRequirementExpressionsValidator = submitRequirementExpressionsValidator;
+ this.updater = updater;
}
@Override
public Response<SubmitRequirementInfo> apply(
ProjectResource rsrc, IdString id, SubmitRequirementInput input)
throws AuthException, BadRequestException, IOException, PermissionBackendException {
- if (!user.get().isIdentifiedUser()) {
- throw new AuthException("Authentication required");
+ String defaultMessage = String.format("Create Submit Requirement %s", id.get());
+ try (var configUpdater =
+ updater.configUpdater(
+ rsrc.getNameKey(),
+ /** message= */
+ null,
+ defaultMessage)) {
+ ProjectConfig config = configUpdater.getConfig();
+ SubmitRequirement submitRequirement = updateConfig(config, id, input);
+
+ configUpdater.commitConfigUpdate();
+ return Response.created(SubmitRequirementJson.format(submitRequirement));
+ } catch (ConfigInvalidException e) {
+ throw new IOException("Failed to read project config", e);
+ } catch (ResourceConflictException e) {
+ throw new BadRequestException("Failed to create submit requirement", e);
}
+ }
- permissionBackend
- .currentUser()
- .project(rsrc.getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
+ SubmitRequirement updateConfig(ProjectConfig config, IdString id, SubmitRequirementInput input)
+ throws ResourceConflictException, BadRequestException {
if (input == null) {
input = new SubmitRequirementInput();
}
@@ -93,23 +87,7 @@
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);
-
- SubmitRequirement submitRequirement = createSubmitRequirement(config, id.get(), input);
-
- md.setMessage(String.format("Create Submit Requirement %s", submitRequirement.name()));
- config.commit(md);
-
- projectCache.evict(rsrc.getProjectState().getProject().getNameKey());
-
- return Response.created(SubmitRequirementJson.format(submitRequirement));
- } catch (ConfigInvalidException e) {
- throw new IOException("Failed to read project config", e);
- } catch (ResourceConflictException e) {
- throw new BadRequestException("Failed to create submit requirement", e);
- }
+ return createSubmitRequirement(config, id.get(), input);
}
public SubmitRequirement createSubmitRequirement(
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
index 8a1927a..fcd72a3 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
@@ -14,84 +14,49 @@
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.BadRequestException;
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;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@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;
+ public DeleteLabel(RepoMetaDataUpdater repoMetaDataUpdater) {
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@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);
-
+ ConfigInvalidException, BadRequestException {
if (input == null) {
input = new InputWithCommitMessage();
}
- try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ rsrc.getProject().getNameKey(), input.commitMessage, "Delete label")) {
+ ProjectConfig config = configUpdater.getConfig();
if (!deleteLabel(config, rsrc.getLabelType().getName())) {
throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
}
-
- if (input.commitMessage != null) {
- md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
- } else {
- md.setMessage("Delete label");
- }
-
- config.commit(md);
+ configUpdater.commitConfigUpdate();
}
- projectCache.evictAndReindex(rsrc.getProject().getProjectState().getProject());
-
return Response.none();
}
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteSubmitRequirement.java b/java/com/google/gerrit/server/restapi/project/DeleteSubmitRequirement.java
index 1be4a5f..64e2399 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteSubmitRequirement.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteSubmitRequirement.java
@@ -15,57 +15,30 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.common.Input;
-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.ProjectPermission;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.SubmitRequirementResource;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
public class DeleteSubmitRequirement implements RestModifyView<SubmitRequirementResource, Input> {
- private final Provider<CurrentUser> user;
- private final PermissionBackend permissionBackend;
- private final MetaDataUpdate.User updateFactory;
- private final ProjectConfig.Factory projectConfigFactory;
- private final ProjectCache projectCache;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
- public DeleteSubmitRequirement(
- 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;
+ public DeleteSubmitRequirement(RepoMetaDataUpdater repoMetaDataUpdater) {
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
public Response<?> apply(SubmitRequirementResource rsrc, Input input) throws Exception {
- if (!user.get().isIdentifiedUser()) {
- throw new AuthException("Authentication required");
- }
-
- permissionBackend
- .currentUser()
- .project(rsrc.getProject().getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
- try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ rsrc.getProject().getNameKey(), null, "Delete submit requirement")) {
+ ProjectConfig config = configUpdater.getConfig();
if (!deleteSubmitRequirement(config, rsrc.getSubmitRequirement().name())) {
// This code is unreachable because the exception is thrown when rsrc was parsed
@@ -75,12 +48,9 @@
IdString.fromDecoded(rsrc.getSubmitRequirement().name())));
}
- md.setMessage("Delete submit requirement");
- config.commit(md);
+ configUpdater.commitConfigUpdate();
}
- projectCache.evict(rsrc.getProject().getProjectState().getProject().getNameKey());
-
return Response.none();
}
diff --git a/java/com/google/gerrit/server/restapi/project/PutDescription.java b/java/com/google/gerrit/server/restapi/project/PutDescription.java
index ec42035..698cf58 100644
--- a/java/com/google/gerrit/server/restapi/project/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/project/PutDescription.java
@@ -14,24 +14,18 @@
package com.google.gerrit.server.restapi.project;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.api.projects.DescriptionInput;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
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.IdentifiedUser;
-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.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
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;
@@ -39,53 +33,30 @@
@Singleton
public class PutDescription implements RestModifyView<ProjectResource, DescriptionInput> {
- private final ProjectCache cache;
- private final Provider<MetaDataUpdate.Server> updateFactory;
- private final PermissionBackend permissionBackend;
- private final ProjectConfig.Factory projectConfigFactory;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
- PutDescription(
- ProjectCache cache,
- Provider<MetaDataUpdate.Server> updateFactory,
- PermissionBackend permissionBackend,
- ProjectConfig.Factory projectConfigFactory) {
- this.cache = cache;
- this.updateFactory = updateFactory;
- this.permissionBackend = permissionBackend;
- this.projectConfigFactory = projectConfigFactory;
+ PutDescription(RepoMetaDataUpdater repoMetaDataUpdater) {
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
public Response<String> apply(ProjectResource resource, DescriptionInput input)
throws AuthException, ResourceConflictException, ResourceNotFoundException, IOException,
- PermissionBackendException {
+ PermissionBackendException, BadRequestException {
if (input == null) {
input = new DescriptionInput(); // Delete would set description to null.
}
- IdentifiedUser user = resource.getUser().asIdentifiedUser();
- permissionBackend
- .user(user)
- .project(resource.getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
- try (MetaDataUpdate md = updateFactory.get().create(resource.getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ resource.getNameKey(), input.commitMessage, "Update description")) {
+ ProjectConfig config = configUpdater.getConfig();
String desc = input.description;
config.updateProject(p -> p.setDescription(Strings.emptyToNull(desc)));
- String msg =
- MoreObjects.firstNonNull(
- Strings.emptyToNull(input.commitMessage), "Update description\n");
- if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(user);
- md.setMessage(msg);
- config.commit(md);
- cache.evictAndReindex(resource.getProjectState().getProject());
- md.getRepository().setGitwebDescription(config.getProject().getDescription());
+ configUpdater.commitConfigUpdate();
+ configUpdater.getRepository().setGitwebDescription(config.getProject().getDescription());
return Strings.isNullOrEmpty(config.getProject().getDescription())
? Response.none()
diff --git a/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java b/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java
index c45a009..5aea2c5 100644
--- a/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java
+++ b/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java
@@ -15,24 +15,28 @@
package com.google.gerrit.server.restapi.project;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.MustBeClosed;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.exceptions.InvalidNameException;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.CreateGroupPermissionSyncer;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeInserter;
+import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.MetaDataUpdate.User;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -49,18 +53,20 @@
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
/** Updates repo refs/meta/config content. */
@Singleton
public class RepoMetaDataUpdater {
- private final CreateGroupPermissionSyncer createGroupPermissionSyncer;
private final Provider<User> metaDataUpdateFactory;
+ private final Provider<CurrentUser> user;
private final ProjectConfig.Factory projectConfigFactory;
private final ProjectCache projectCache;
private final ChangeInserter.Factory changeInserterFactory;
@@ -69,38 +75,61 @@
private final BatchUpdate.Factory updateFactory;
private final PermissionBackend permissionBackend;
+ private final ChangeJson.Factory jsonFactory;
@Inject
RepoMetaDataUpdater(
- CreateGroupPermissionSyncer createGroupPermissionSyncer,
Provider<User> metaDataUpdateFactory,
+ Provider<CurrentUser> user,
ProjectConfig.Factory projectConfigFactory,
ProjectCache projectCache,
ChangeInserter.Factory changeInserterFactory,
Sequences seq,
BatchUpdate.Factory updateFactory,
- PermissionBackend permissionBackend) {
- this.createGroupPermissionSyncer = createGroupPermissionSyncer;
+ PermissionBackend permissionBackend,
+ ChangeJson.Factory jsonFactory) {
this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.user = user;
this.projectConfigFactory = projectConfigFactory;
this.projectCache = projectCache;
this.changeInserterFactory = changeInserterFactory;
this.seq = seq;
this.updateFactory = updateFactory;
this.permissionBackend = permissionBackend;
+ this.jsonFactory = jsonFactory;
}
- public Change updateAndCreateChangeForReview(
- Project.NameKey projectName,
- CurrentUser user,
- String message,
- ProjectConfigUpdater projectConfigUpdater)
- throws ConfigInvalidException, IOException, RestApiException, UpdateException,
- InvalidNameException, PermissionBackendException {
- checkArgument(!message.isBlank(), "The message must not be empty");
- message = validateMessage(message);
-
- PermissionBackend.ForProject forProject = permissionBackend.user(user).project(projectName);
+ /**
+ * Returns a creator for creating project config changes.
+ *
+ * <p>The method checks that user has required permissions.
+ *
+ * <p>Usage:
+ *
+ * <pre>{@code
+ * try(var changeCreator =
+ * repoMetaDataUpdater.configChangeCreator(projectName, message, defaultMessage)) {
+ * ProjectConfig config = changeCreator.getConfig();
+ * // ... update project config
+ * // Create change - if the createChange method is not called, all updates are ignored and no
+ * // change is created.
+ * Response<ChangeInfo> result = changeCreator.createChange();
+ * }
+ * }</pre>
+ *
+ * @param projectName the name of the project whose config should be updated
+ * @param message the user-provided commit message. If it is not provided (i.e. it is null or
+ * empty) - the {@code defaultMessage} is used.
+ * @param defaultMessage the default commit message if the user doesn't provide one.
+ */
+ @MustBeClosed
+ public ConfigChangeCreator configChangeCreator(
+ Project.NameKey projectName, @Nullable String message, String defaultMessage)
+ throws PermissionBackendException, AuthException, ResourceConflictException, IOException,
+ ConfigInvalidException {
+ message = validateMessage(message, defaultMessage);
+ PermissionBackend.ForProject forProject =
+ permissionBackend.user(user.get()).project(projectName);
if (!check(forProject, ProjectPermission.READ_CONFIG)) {
throw new AuthException(RefNames.REFS_CONFIG + " not visible");
}
@@ -112,15 +141,153 @@
}
}
projectCache.get(projectName).orElseThrow(illegalState(projectName)).checkStatePermitsWrite();
-
- try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) {
- ProjectConfig config = projectConfigFactory.read(md);
- ObjectId oldCommit = config.getRevision();
- String oldCommitSha1 = oldCommit == null ? null : oldCommit.getName();
-
- projectConfigUpdater.update(config);
- md.setMessage(message);
+ // The MetaDataUpdate instance gets closed in the ConfigChangeCreator.close() method.
+ MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName);
+ try {
md.setInsertChangeId(true);
+ md.setMessage(message);
+ ProjectConfig config = projectConfigFactory.read(md);
+ return new ConfigChangeCreator(md, projectName, user.get(), config);
+ } catch (Throwable t) {
+ try (md) {
+ throw t;
+ }
+ }
+ }
+
+ /**
+ * Returns an updater for updating project config without review.
+ *
+ * <p>The method checks that user has required permissions.
+ *
+ * <p>When the update is saved (using the {@link ConfigUpdater#commitConfigUpdate} method), the
+ * project cache is updated automatically.
+ *
+ * <p>Usage:
+ *
+ * <pre>{@code
+ * try(var configUpdater =
+ * repoMetaDataUpdater.configUpdater(projectName, message, defaultMessage)) {
+ * ProjectConfig config = changeCreator.getConfig();
+ * // ... update project config
+ * // Save updated config - if the commitConfigUpdate method is not called, all updates are ignored.
+ * configUpdater.commitConfigUpdate();
+ * }
+ * }</pre>
+ *
+ * @param projectName the name of the project whose config should be updated
+ * @param message the user-provided commit message. If it is not provided (i.e. it is null or
+ * empty) - the {@code defaultMessage} is used.
+ * @param defaultMessage the default commit message if the user doesn't provide one.
+ */
+ @MustBeClosed
+ public ConfigUpdater configUpdater(
+ Project.NameKey projectName, @Nullable String message, String defaultMessage)
+ throws AuthException, PermissionBackendException, ConfigInvalidException, IOException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ permissionBackend.user(user.get()).project(projectName).check(ProjectPermission.WRITE_CONFIG);
+ return configUpdaterWithoutPermissionsCheck(projectName, message, defaultMessage);
+ }
+
+ /**
+ * Returns an updater for updating project config without review and skips some permissions
+ * checks.
+ *
+ * <p>The method doesn't do any permissions checks. It should be used only when standard
+ * permissions checks from {@link #configUpdater} can't be used.
+ *
+ * <p>See {@link #configUpdater} for details.
+ */
+ @MustBeClosed
+ public ConfigUpdater configUpdaterWithoutPermissionsCheck(
+ Project.NameKey projectName, @Nullable String message, String defaultMessage)
+ throws IOException, ConfigInvalidException {
+ message = validateMessage(message, defaultMessage);
+ // The MetaDataUpdate instance gets closed in the ConfigUpdater.close() method.
+ MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName);
+ try {
+ ProjectConfig config = projectConfigFactory.read(md);
+ md.setMessage(message);
+ return new ConfigUpdater(md, config);
+ } catch (Throwable t) {
+ try (md) {
+ throw t;
+ }
+ }
+ }
+
+ /**
+ * Updater for a project config without review.
+ *
+ * <p>See {@link #configUpdater} and {@link #configUpdaterWithoutPermissionsCheck} for details and
+ * usages.
+ */
+ public class ConfigUpdater implements AutoCloseable {
+ private final MetaDataUpdate md;
+ private final ProjectConfig config;
+
+ private ConfigUpdater(MetaDataUpdate md, ProjectConfig config) {
+ this.md = md;
+ this.config = config;
+ }
+
+ public ProjectConfig getConfig() {
+ return config;
+ }
+
+ public void commitConfigUpdate() throws IOException {
+ config.commit(md);
+ projectCache.evictAndReindex(config.getProject());
+ }
+
+ public Repository getRepository() {
+ return md.getRepository();
+ }
+
+ @Override
+ public void close() {
+ md.close();
+ }
+ }
+
+ /**
+ * Creates a change for a project config update.
+ *
+ * <p>See {@link #createChange} for details and usages.
+ */
+ public class ConfigChangeCreator implements AutoCloseable {
+ private final MetaDataUpdate md;
+ private final String oldCommitSha1;
+ private final Project.NameKey projectName;
+ private final CurrentUser user;
+ private final ProjectConfig config;
+ private boolean changeCreated;
+
+ private ConfigChangeCreator(
+ MetaDataUpdate md, Project.NameKey projectName, CurrentUser user, ProjectConfig config) {
+ this.md = md;
+ this.config = config;
+ this.projectName = projectName;
+ this.user = user;
+ ObjectId oldCommit = config.getRevision();
+ oldCommitSha1 = oldCommit == null ? null : oldCommit.getName();
+ }
+
+ @Override
+ public void close() {
+ md.close();
+ }
+
+ public ProjectConfig getConfig() {
+ return config;
+ }
+
+ public Response<ChangeInfo> createChange()
+ throws IOException, UpdateException, RestApiException {
+ checkState(!changeCreated, "Change has been already created");
+ changeCreated = true;
Change.Id changeId = Change.id(seq.nextChangeId());
try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
@@ -140,62 +307,38 @@
ChangeInserter ins = newInserter(changeId, commit);
bu.insertChange(ins);
bu.execute();
- return ins.getChange();
+ Change change = ins.getChange();
+ return Response.created(jsonFactory.noOptions().format(change));
}
}
}
- }
- public void updateWithoutReview(
- Project.NameKey projectName, String message, ProjectConfigUpdater projectConfigUpdater)
- throws ConfigInvalidException, IOException, PermissionBackendException, AuthException,
- ResourceConflictException, InvalidNameException, BadRequestException {
- updateWithoutReview(
- projectName, message, /*skipPermissionsCheck=*/ false, projectConfigUpdater);
- }
-
- public void updateWithoutReview(
- Project.NameKey projectName,
- String message,
- boolean skipPermissionsCheck,
- ProjectConfigUpdater projectConfigUpdater)
- throws ConfigInvalidException, IOException, PermissionBackendException, AuthException,
- ResourceConflictException, InvalidNameException, BadRequestException {
- message = validateMessage(message);
- if (!skipPermissionsCheck) {
- permissionBackend.currentUser().project(projectName).check(ProjectPermission.WRITE_CONFIG);
- }
-
- try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) {
- ProjectConfig config = projectConfigFactory.read(md);
-
- projectConfigUpdater.update(config);
- md.setMessage(message);
- config.commit(md);
- projectCache.evictAndReindex(config.getProject());
- createGroupPermissionSyncer.syncIfNeeded();
+ // ProjectConfig doesn't currently support fusing into a BatchUpdate.
+ @SuppressWarnings("deprecation")
+ private ChangeInserter newInserter(Change.Id changeId, RevCommit commit) {
+ return changeInserterFactory
+ .create(changeId, commit, RefNames.REFS_CONFIG)
+ .setMessage(
+ // Same message as in ReceiveCommits.CreateRequest.
+ ApprovalsUtil.renderMessageWithApprovals(1, ImmutableMap.of(), ImmutableMap.of()))
+ .setValidate(false)
+ .setUpdateRef(false);
}
}
- private String validateMessage(String message) {
+ private String validateMessage(@Nullable String message, String defaultMessage) {
+ if (Strings.isNullOrEmpty(message)) {
+ message = defaultMessage;
+ } else {
+ message = message.trim();
+ }
+ checkArgument(!message.isBlank(), "The message must not be empty");
if (!message.endsWith("\n")) {
return message + "\n";
}
return message;
}
- // ProjectConfig doesn't currently support fusing into a BatchUpdate.
- @SuppressWarnings("deprecation")
- private ChangeInserter newInserter(Change.Id changeId, RevCommit commit) {
- return changeInserterFactory
- .create(changeId, commit, RefNames.REFS_CONFIG)
- .setMessage(
- // Same message as in ReceiveCommits.CreateRequest.
- ApprovalsUtil.renderMessageWithApprovals(1, ImmutableMap.of(), ImmutableMap.of()))
- .setValidate(false)
- .setUpdateRef(false);
- }
-
private boolean check(PermissionBackend.ForProject perm, ProjectPermission p)
throws PermissionBackendException {
try {
@@ -205,11 +348,4 @@
return false;
}
}
-
- @FunctionalInterface
- public interface ProjectConfigUpdater {
- void update(ProjectConfig config)
- throws BadRequestException, InvalidNameException, PermissionBackendException,
- ResourceConflictException, AuthException;
- }
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccess.java b/java/com/google/gerrit/server/restapi/project/SetAccess.java
index 75fe280..65851c0 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccess.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.restapi.project;
-import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.entities.AccessSection;
@@ -29,12 +28,15 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CreateGroupPermissionSyncer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.restapi.project.RepoMetaDataUpdater.ConfigUpdater;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -49,6 +51,7 @@
private final Provider<IdentifiedUser> identifiedUser;
private final SetAccessUtil accessUtil;
private final RepoMetaDataUpdater repoMetaDataUpdater;
+ private final CreateGroupPermissionSyncer createGroupPermissionSyncer;
@Inject
private SetAccess(
@@ -57,6 +60,7 @@
GetAccess getAccess,
Provider<IdentifiedUser> identifiedUser,
SetAccessUtil accessUtil,
+ CreateGroupPermissionSyncer createGroupPermissionSyncer,
RepoMetaDataUpdater repoMetaDataUpdater) {
this.groupBackend = groupBackend;
this.permissionBackend = permissionBackend;
@@ -64,6 +68,7 @@
this.identifiedUser = identifiedUser;
this.accessUtil = accessUtil;
this.repoMetaDataUpdater = repoMetaDataUpdater;
+ this.createGroupPermissionSyncer = createGroupPermissionSyncer;
}
@Override
@@ -75,42 +80,40 @@
accessUtil.getAccessSections(input.remove, /* rejectNonResolvableGroups= */ false);
ImmutableList<AccessSection> additions =
accessUtil.getAccessSections(input.add, /* rejectNonResolvableGroups= */ true);
- String message = !Strings.isNullOrEmpty(input.message) ? input.message : "Modify access rules";
- try {
- this.repoMetaDataUpdater.updateWithoutReview(
+
+ try (ConfigUpdater updater =
+ repoMetaDataUpdater.configUpdaterWithoutPermissionsCheck(
+ rsrc.getNameKey(), input.message, "Modify access rules")) {
+ ProjectConfig config = updater.getConfig();
+ boolean checkedAdmin = false;
+ for (AccessSection section : Iterables.concat(additions, removals)) {
+ boolean isGlobalCapabilities = AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
+ if (isGlobalCapabilities) {
+ if (!checkedAdmin) {
+ permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+ checkedAdmin = true;
+ }
+ } else {
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getNameKey())
+ .ref(section.getName())
+ .check(RefPermission.WRITE_CONFIG);
+ }
+ }
+
+ accessUtil.validateChanges(config, removals, additions);
+ accessUtil.applyChanges(config, removals, additions);
+
+ accessUtil.setParentName(
+ identifiedUser.get(),
+ config,
rsrc.getNameKey(),
- message,
- /*skipPermissionsCheck=*/ true,
- config -> {
- // Check that the user has the right permissions.
- boolean checkedAdmin = false;
- for (AccessSection section : Iterables.concat(additions, removals)) {
- boolean isGlobalCapabilities =
- AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
- if (isGlobalCapabilities) {
- if (!checkedAdmin) {
- permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
- checkedAdmin = true;
- }
- } else {
- permissionBackend
- .currentUser()
- .project(rsrc.getNameKey())
- .ref(section.getName())
- .check(RefPermission.WRITE_CONFIG);
- }
- }
+ input.parent == null ? null : Project.nameKey(input.parent),
+ !checkedAdmin);
- accessUtil.validateChanges(config, removals, additions);
- accessUtil.applyChanges(config, removals, additions);
-
- accessUtil.setParentName(
- identifiedUser.get(),
- config,
- rsrc.getNameKey(),
- input.parent == null ? null : Project.nameKey(input.parent),
- !checkedAdmin);
- });
+ updater.commitConfigUpdate();
+ createGroupPermissionSyncer.syncIfNeeded();
} catch (InvalidNameException e) {
throw new BadRequestException(e.toString());
} catch (ConfigInvalidException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
index 853d7df..a46ee32 100644
--- a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.restapi.project;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.SetDashboardInput;
@@ -25,12 +24,8 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-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.DashboardResource;
-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;
@@ -41,30 +36,21 @@
import org.kohsuke.args4j.Option;
class SetDefaultDashboard implements RestModifyView<DashboardResource, SetDashboardInput> {
- private final ProjectCache cache;
- private final MetaDataUpdate.Server updateFactory;
private final DashboardsCollection dashboards;
private final Provider<GetDashboard> get;
- private final PermissionBackend permissionBackend;
- private final ProjectConfig.Factory projectConfigFactory;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Option(name = "--inherited", usage = "set dashboard inherited by children")
boolean inherited;
@Inject
SetDefaultDashboard(
- ProjectCache cache,
- MetaDataUpdate.Server updateFactory,
DashboardsCollection dashboards,
Provider<GetDashboard> get,
- PermissionBackend permissionBackend,
- ProjectConfig.Factory projectConfigFactory) {
- this.cache = cache;
- this.updateFactory = updateFactory;
+ RepoMetaDataUpdater repoMetaDataUpdater) {
this.dashboards = dashboards;
this.get = get;
- this.permissionBackend = permissionBackend;
- this.projectConfigFactory = projectConfigFactory;
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
@@ -75,11 +61,6 @@
}
input.id = Strings.emptyToNull(input.id);
- permissionBackend
- .user(rsrc.getUser())
- .project(rsrc.getProjectState().getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
DashboardResource target = null;
if (input.id != null) {
try {
@@ -93,29 +74,22 @@
throw new ResourceConflictException(e.getMessage());
}
}
+ String defaultMessage =
+ input.id == null
+ ? "Removed default dashboard.\n"
+ : String.format("Changed default dashboard to %s.\n", input.id);
- try (MetaDataUpdate md = updateFactory.create(rsrc.getProjectState().getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ rsrc.getProjectState().getNameKey(), input.commitMessage, defaultMessage)) {
+ ProjectConfig config = configUpdater.getConfig();
String id = input.id;
if (inherited) {
config.updateProject(p -> p.setDefaultDashboard(id));
} else {
config.updateProject(p -> p.setLocalDefaultDashboard(id));
}
-
- String msg =
- MoreObjects.firstNonNull(
- Strings.emptyToNull(input.commitMessage),
- input.id == null
- ? "Removed default dashboard.\n"
- : String.format("Changed default dashboard to %s.\n", input.id));
- if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(rsrc.getUser().asIdentifiedUser());
- md.setMessage(msg);
- config.commit(md);
- cache.evictAndReindex(rsrc.getProjectState().getProject());
+ configUpdater.commitConfigUpdate();
if (target != null) {
Response<DashboardInfo> response = get.get().apply(target);
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index edd165d..1553eda 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -24,18 +24,12 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.index.query.QueryParseException;
-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.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.query.approval.ApprovalQueryBuilder;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Optional;
@@ -43,63 +37,36 @@
@Singleton
public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionInput> {
- private final Provider<CurrentUser> user;
- private final PermissionBackend permissionBackend;
- private final MetaDataUpdate.User updateFactory;
- private final ProjectConfig.Factory projectConfigFactory;
- private final ProjectCache projectCache;
private final ApprovalQueryBuilder approvalQueryBuilder;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
public SetLabel(
- Provider<CurrentUser> user,
- PermissionBackend permissionBackend,
- MetaDataUpdate.User updateFactory,
- ProjectConfig.Factory projectConfigFactory,
- ProjectCache projectCache,
- ApprovalQueryBuilder approvalQueryBuilder) {
- this.user = user;
- this.permissionBackend = permissionBackend;
- this.updateFactory = updateFactory;
- this.projectConfigFactory = projectConfigFactory;
- this.projectCache = projectCache;
+ ApprovalQueryBuilder approvalQueryBuilder, RepoMetaDataUpdater repoMetaDataUpdater) {
this.approvalQueryBuilder = approvalQueryBuilder;
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
public Response<LabelDefinitionInfo> apply(LabelResource rsrc, LabelDefinitionInput input)
throws AuthException, BadRequestException, ResourceConflictException,
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 LabelDefinitionInput();
}
LabelType labelType = rsrc.getLabelType();
- try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ rsrc.getProject().getNameKey(), input.commitMessage, "Update label")) {
+ ProjectConfig config = configUpdater.getConfig();
if (updateLabel(config, labelType, input)) {
- if (input.commitMessage != null) {
- md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
- } else {
- md.setMessage("Update label");
- }
String newName = Strings.nullToEmpty(input.name).trim();
labelType =
config.getLabelSections().get(newName.isEmpty() ? labelType.getName() : newName);
-
- config.commit(md);
- projectCache.evictAndReindex(rsrc.getProject().getProjectState().getProject());
+ configUpdater.commitConfigUpdate();
}
}
return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType));
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index ef31dc5..ad8bc8a 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -38,7 +38,6 @@
import com.google.gerrit.server.config.ConfigUpdatedEvent.UpdateResult;
import com.google.gerrit.server.config.GerritConfigListener;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -48,7 +47,6 @@
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
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;
@@ -60,29 +58,26 @@
implements RestModifyView<ProjectResource, ParentInput>, GerritConfigListener {
private final ProjectCache cache;
private final PermissionBackend permissionBackend;
- private final Provider<MetaDataUpdate.Server> updateFactory;
private final AllProjectsName allProjects;
private final AllUsersName allUsers;
- private final ProjectConfig.Factory projectConfigFactory;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
private volatile boolean allowProjectOwnersToChangeParent;
@Inject
SetParent(
ProjectCache cache,
PermissionBackend permissionBackend,
- Provider<MetaDataUpdate.Server> updateFactory,
AllProjectsName allProjects,
AllUsersName allUsers,
- ProjectConfig.Factory projectConfigFactory,
- @GerritServerConfig Config config) {
+ @GerritServerConfig Config config,
+ RepoMetaDataUpdater repoMetaDataUpdater) {
this.cache = cache;
this.permissionBackend = permissionBackend;
- this.updateFactory = updateFactory;
this.allProjects = allProjects;
this.allUsers = allUsers;
- this.projectConfigFactory = projectConfigFactory;
this.allowProjectOwnersToChangeParent =
config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
@@ -101,20 +96,14 @@
String parentName =
MoreObjects.firstNonNull(Strings.emptyToNull(input.parent), allProjects.get());
validateParentUpdate(rsrc.getProjectState().getNameKey(), user, parentName, checkIfAdmin);
- try (MetaDataUpdate md = updateFactory.get().create(rsrc.getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdaterWithoutPermissionsCheck(
+ rsrc.getNameKey(),
+ input.commitMessage,
+ String.format("Changed parent to %s.\n", parentName))) {
+ ProjectConfig config = configUpdater.getConfig();
config.updateProject(p -> p.setParent(parentName));
-
- String msg = Strings.emptyToNull(input.commitMessage);
- if (msg == null) {
- msg = String.format("Changed parent to %s.\n", parentName);
- } else if (!msg.endsWith("\n")) {
- msg += "\n";
- }
- md.setAuthor(user);
- md.setMessage(msg);
- config.commit(md);
- cache.evictAndReindex(rsrc.getProjectState().getProject());
+ configUpdater.commitConfigUpdate();
Project.NameKey parent = config.getProject().getParent(allProjects);
requireNonNull(parent);
diff --git a/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java b/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
index 3e1104e..7eb2665 100644
--- a/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
+++ b/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
@@ -24,19 +24,13 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
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.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.SubmitRequirementExpressionsValidator;
import com.google.gerrit.server.project.SubmitRequirementJson;
import com.google.gerrit.server.project.SubmitRequirementResource;
import com.google.gerrit.server.project.SubmitRequirementsUtil;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Optional;
@@ -48,42 +42,21 @@
@Singleton
public class UpdateSubmitRequirement
implements RestModifyView<SubmitRequirementResource, SubmitRequirementInput> {
- private final Provider<CurrentUser> user;
- private final PermissionBackend permissionBackend;
- private final MetaDataUpdate.User updateFactory;
- private final ProjectConfig.Factory projectConfigFactory;
- private final ProjectCache projectCache;
private final SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator;
+ private final RepoMetaDataUpdater repoMetaDataUpdater;
@Inject
public UpdateSubmitRequirement(
- Provider<CurrentUser> user,
- PermissionBackend permissionBackend,
- MetaDataUpdate.User updateFactory,
- ProjectConfig.Factory projectConfigFactory,
- ProjectCache projectCache,
- SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator) {
- this.user = user;
- this.permissionBackend = permissionBackend;
- this.updateFactory = updateFactory;
- this.projectConfigFactory = projectConfigFactory;
- this.projectCache = projectCache;
+ SubmitRequirementExpressionsValidator submitRequirementExpressionsValidator,
+ RepoMetaDataUpdater repoMetaDataUpdater) {
this.submitRequirementExpressionsValidator = submitRequirementExpressionsValidator;
+ this.repoMetaDataUpdater = repoMetaDataUpdater;
}
@Override
public Response<SubmitRequirementInfo> apply(
SubmitRequirementResource rsrc, SubmitRequirementInput input)
throws AuthException, BadRequestException, PermissionBackendException, IOException {
- if (!user.get().isIdentifiedUser()) {
- throw new AuthException("Authentication required");
- }
-
- permissionBackend
- .currentUser()
- .project(rsrc.getProject().getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
-
if (input == null) {
input = new SubmitRequirementInput();
}
@@ -92,16 +65,17 @@
throw new BadRequestException("name in input must match name in URL");
}
- try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
- ProjectConfig config = projectConfigFactory.read(md);
+ try (var configUpdater =
+ repoMetaDataUpdater.configUpdater(
+ rsrc.getProject().getNameKey(),
+ null,
+ String.format("Update Submit Requirement %s", rsrc.getSubmitRequirement().name()))) {
+ ProjectConfig config = configUpdater.getConfig();
SubmitRequirement submitRequirement =
- createSubmitRequirement(config, rsrc.getSubmitRequirement().name(), input);
+ updateSubmitRequirement(config, rsrc.getSubmitRequirement().name(), input);
- md.setMessage(String.format("Update Submit Requirement %s", submitRequirement.name()));
- config.commit(md);
-
- projectCache.evict(rsrc.getProject().getNameKey());
+ configUpdater.commitConfigUpdate();
return Response.created(SubmitRequirementJson.format(submitRequirement));
} catch (ConfigInvalidException e) {
@@ -109,7 +83,7 @@
}
}
- public SubmitRequirement createSubmitRequirement(
+ public SubmitRequirement updateSubmitRequirement(
ProjectConfig config, String name, SubmitRequirementInput input) throws BadRequestException {
validateSRName(name);
if (Strings.isNullOrEmpty(input.submittabilityExpression)) {
diff --git a/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java b/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
index b9cbbcd..2ce8f78 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
@@ -25,6 +25,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
@@ -102,6 +103,10 @@
DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().dashboard(info.id).setDefault();
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG,
+ "Administrator",
+ String.format("Changed default dashboard to %s.", info.id));
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
assertThat(project().defaultDashboard().get().id).isEqualTo(info.id);
}
@@ -111,10 +116,16 @@
DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().defaultDashboard(info.id);
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG,
+ "Administrator",
+ String.format("Changed default dashboard to %s.", info.id));
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
assertThat(project().defaultDashboard().get().id).isEqualTo(info.id);
project().removeDefaultDashboard();
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG, "Administrator", "Removed default dashboard.");
assertThat(project().dashboard(info.id).get().isDefault).isNull();
assertThrows(ResourceNotFoundException.class, () -> project().defaultDashboard().get());
diff --git a/javatests/com/google/gerrit/acceptance/api/project/PutDescriptionIT.java b/javatests/com/google/gerrit/acceptance/api/project/PutDescriptionIT.java
new file mode 100644
index 0000000..befb311
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/project/PutDescriptionIT.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.projects.DescriptionInput;
+import org.junit.Test;
+
+public class PutDescriptionIT extends AbstractDaemonTest {
+ @Test
+ public void setDescription() throws Exception {
+ DescriptionInput input = new DescriptionInput();
+ input.description = "test project description";
+ gApi.projects().name(project.get()).description(input);
+ assertThat(gApi.projects().name(project.get()).description())
+ .isEqualTo("test project description");
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG, "Administrator", "Update description");
+ }
+
+ @Test
+ public void setDescriptionWithCustomCommitMessage() throws Exception {
+ DescriptionInput input = new DescriptionInput();
+ input.description = "test project description with test commit message";
+ input.commitMessage = "test commit message";
+ gApi.projects().name(project.get()).description(input);
+ assertThat(gApi.projects().name(project.get()).description())
+ .isEqualTo("test project description with test commit message");
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG, "Administrator", "test commit message");
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
index 2bdbe50..a839b6a 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
@@ -25,6 +25,7 @@
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -89,12 +90,16 @@
gApi.projects().name(project.get()).parent(parent);
assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG, "Administrator", String.format("Changed parent to %s.", parent));
// When the parent name is not explicitly set, it should be
// set to "All-Projects".
gApi.projects().name(project.get()).parent(null);
assertThat(gApi.projects().name(project.get()).parent())
.isEqualTo(AllProjectsNameProvider.DEFAULT);
+ assertLastCommitAuthorAndShortMessage(
+ RefNames.REFS_CONFIG, "Administrator", "Changed parent to All-Projects.");
}
@Test