Merge "GetRelated: Add some debug logs"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index c97e4a4..036dfbf 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -342,8 +342,14 @@
=== Elasticsearch
Successfully running the Elasticsearch tests requires Docker, and
-may require setting the local
-link:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html[virtual memory,role=external,window=_blank].
+may require setting the local virtual memory on
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html[linux,role=external,window=_blank] and
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_set_vm_max_map_count_to_at_least_262144[macOS,role=external,window=_blank].
+
+On macOS, if using link:https://docs.docker.com/docker-for-mac/[Docker Desktop,role=external,window=_blank],
+the effective memory value can be set in the Preferences, under the Advanced tab.
+The default value usually does not suffice and is causing premature container exits.
+That default is currently 2 GB and should be set to at least 5 (GB).
If Docker is not available, the Elasticsearch tests will be skipped.
Note that Bazel currently does not show
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index dcd8121..d5abbe3 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1457,6 +1457,10 @@
Reverts a change.
+The subject of the newly created change will be
+'Revert "<subject-of-reverted-change>"'. If the subject of the change reverted is
+above 63 characters, it will be cut down to 59 characters with "..." in the end.
+
The request body does not need to include a link:#revert-input[
RevertInput] entity if no review comment is added.
@@ -1515,7 +1519,9 @@
Creates open revert changes for all of the changes of a certain submission.
-The subject of each revert change will be "Revert <subject-of-reverted-change".
+The subject of each revert change will be 'Revert "<subject-of-reverted-change"'.
+If the subject is above 63 characters, the subject will be cut to 59 characters
+with "..." in the end.
Details for the revert can be specified in the request body inside a link:#revert-input[
RevertInput] The topic of all created revert changes will be
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
index 25eb7a8..6bd4b73 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
@@ -150,7 +150,22 @@
* @param newContent the desired content of the file
* @throws RestApiException if the content of the file couldn't be modified
*/
- void modifyFile(String filePath, RawInput newContent) throws RestApiException;
+ default void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ FileContentInput input = new FileContentInput();
+ input.content = newContent;
+ modifyFile(filePath, input);
+ }
+
+ /**
+ * Modify the contents of the specified file of the change edit. If no content is provided, the
+ * content of the file is erased but the file isn't deleted. If the change edit doesn't exist, it
+ * will be created based on the current patch set of the change.
+ *
+ * @param filePath the path of the file which should be modified
+ * @param input the desired content of the file
+ * @throws RestApiException if the content of the file couldn't be modified
+ */
+ void modifyFile(String filePath, FileContentInput input) throws RestApiException;
/**
* Deletes the specified file from the change edit. If the change edit doesn't exist, it will be
@@ -235,7 +250,7 @@
}
@Override
- public void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ public void modifyFile(String filePath, FileContentInput input) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/api/changes/FileContentInput.java b/java/com/google/gerrit/extensions/api/changes/FileContentInput.java
new file mode 100644
index 0000000..93c253d
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/changes/FileContentInput.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.RawInput;
+
+/** Content to be added to a file (new or existing) via change edit. */
+public class FileContentInput {
+ @DefaultInput public RawInput content;
+}
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index fad3f91..f390ebc 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -114,10 +114,12 @@
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.GroupAuditService;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.PerformanceLogContext;
import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -718,7 +720,10 @@
ViewData viewData,
ETagView<RestResource> view,
RestResource rsrc) {
- try {
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "RestApiServlet#getEtagWithRetry:view",
+ Metadata.builder().restViewName(getViewName(viewData)).build())) {
return invokeRestEndpointWithRetry(
req,
traceContext,
@@ -733,7 +738,10 @@
private String getEtagWithRetry(
HttpServletRequest req, TraceContext traceContext, RestResource.HasETag rsrc) {
- try {
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "RestApiServlet#getEtagWithRetry:resource",
+ Metadata.builder().restViewName(rsrc.getClass().getSimpleName()).build())) {
return invokeRestEndpointWithRetry(
req,
traceContext,
@@ -1735,43 +1743,47 @@
private long handleException(
TraceContext traceContext, Throwable err, HttpServletRequest req, HttpServletResponse res)
throws IOException {
- logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
- if (!res.isCommitted()) {
- res.reset();
- traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
- ImmutableList<String> userMessages =
- globals.exceptionHooks.stream()
- .map(h -> h.getUserMessage(err))
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(toImmutableList());
+ if (res.isCommitted()) {
+ logger.atSevere().withCause(err).log(
+ "Error in %s %s, response already committed", req.getMethod(), uriForLogging(req));
+ return 0;
+ }
- Optional<Integer> statusCode =
- globals.exceptionHooks.stream()
- .map(h -> h.getStatusCode(err))
- .filter(Optional::isPresent)
- .map(Optional::get)
- .findFirst();
- if (statusCode.isPresent() && statusCode.get() < 400) {
- StringBuilder msg = new StringBuilder();
- if (userMessages.size() == 1) {
- msg.append(userMessages.get(0));
- } else {
- userMessages.forEach(m -> msg.append("\n* ").append(m));
- }
+ res.reset();
+ traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
+ ImmutableList<String> userMessages =
+ globals.exceptionHooks.stream()
+ .map(h -> h.getUserMessage(err))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableList());
- res.setStatus(statusCode.get());
- logger.atFinest().withCause(err).log("REST call finished: %d", statusCode.get().intValue());
- return replyText(req, res, true, msg.toString());
- }
-
- StringBuilder msg = new StringBuilder("Internal server error");
- if (!userMessages.isEmpty()) {
+ Optional<Integer> statusCode =
+ globals.exceptionHooks.stream()
+ .map(h -> h.getStatusCode(err))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst();
+ if (statusCode.isPresent() && statusCode.get() < 400) {
+ StringBuilder msg = new StringBuilder();
+ if (userMessages.size() == 1) {
+ msg.append(userMessages.get(0));
+ } else {
userMessages.forEach(m -> msg.append("\n* ").append(m));
}
- return replyError(req, res, statusCode.orElse(SC_INTERNAL_SERVER_ERROR), msg.toString(), err);
+
+ res.setStatus(statusCode.get());
+ logger.atFinest().withCause(err).log("REST call finished: %d", statusCode.get().intValue());
+ return replyText(req, res, true, msg.toString());
}
- return 0;
+
+ logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
+
+ StringBuilder msg = new StringBuilder("Internal server error");
+ if (!userMessages.isEmpty()) {
+ userMessages.forEach(m -> msg.append("\n* ").append(m));
+ }
+ return replyError(req, res, statusCode.orElse(SC_INTERNAL_SERVER_ERROR), msg.toString(), err);
}
private static String uriForLogging(HttpServletRequest req) {
diff --git a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
index 7f0feba..1b0f0c5 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import com.google.gerrit.extensions.api.changes.ChangeEditApi;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.client.ChangeEditDetailOption;
import com.google.gerrit.extensions.common.EditInfo;
@@ -24,7 +25,6 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -200,9 +200,9 @@
}
@Override
- public void modifyFile(String filePath, RawInput newContent) throws RestApiException {
+ public void modifyFile(String filePath, FileContentInput input) throws RestApiException {
try {
- changeEditsPut.apply(changeResource, filePath, newContent);
+ changeEditsPut.apply(changeResource, filePath, input);
} catch (Exception e) {
throw asRestApiException("Cannot modify file of change edit", e);
}
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java
index 476037b..393ccb7 100644
--- a/java/com/google/gerrit/server/git/CommitUtil.java
+++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -146,12 +146,14 @@
revertCommitBuilder.setCommitter(authorIdent);
Change changeToRevert = notes.getChange();
+ String subject = changeToRevert.getSubject();
+ if (subject.length() > 63) {
+ subject = subject.substring(0, 59) + "...";
+ }
if (message == null) {
message =
MessageFormat.format(
- ChangeMessages.get().revertChangeDefaultMessage,
- changeToRevert.getSubject(),
- patch.commitId().name());
+ ChangeMessages.get().revertChangeDefaultMessage, subject, patch.commitId().name());
}
if (generatedChangeId != null) {
revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, generatedChangeId, true));
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index 1808fa6..e220f35 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -19,6 +19,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.Input;
@@ -109,7 +110,7 @@
*/
@Singleton
public static class Create
- implements RestCollectionCreateView<ChangeResource, ChangeEditResource, Put.Input> {
+ implements RestCollectionCreateView<ChangeResource, ChangeEditResource, FileContentInput> {
private final Put putEdit;
@Inject
@@ -118,9 +119,9 @@
}
@Override
- public Response<?> apply(ChangeResource resource, IdString id, Put.Input input)
+ public Response<?> apply(ChangeResource resource, IdString id, FileContentInput input)
throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
- putEdit.apply(resource, id.get(), input.content);
+ putEdit.apply(resource, id.get(), input);
return Response.none();
}
}
@@ -267,11 +268,7 @@
/** Put handler that is activated when PUT request is called on collection element. */
@Singleton
- public static class Put implements RestModifyView<ChangeEditResource, Put.Input> {
- public static class Input {
- @DefaultInput public RawInput content;
- }
-
+ public static class Put implements RestModifyView<ChangeEditResource, FileContentInput> {
private final ChangeEditModifier editModifier;
private final GitRepositoryManager repositoryManager;
@@ -282,17 +279,18 @@
}
@Override
- public Response<?> apply(ChangeEditResource rsrc, Input input)
+ public Response<?> apply(ChangeEditResource rsrc, FileContentInput input)
throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
- return apply(rsrc.getChangeResource(), rsrc.getPath(), input.content);
+ return apply(rsrc.getChangeResource(), rsrc.getPath(), input);
}
- public Response<?> apply(ChangeResource rsrc, String path, RawInput newContent)
+ public Response<?> apply(ChangeResource rsrc, String path, FileContentInput input)
throws ResourceConflictException, AuthException, IOException, PermissionBackendException {
if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
throw new ResourceConflictException("Invalid path: " + path);
}
+ RawInput newContent = input.content;
try (Repository repository = repositoryManager.openRepository(rsrc.getProject())) {
editModifier.modifyFile(repository, rsrc.getNotes(), path, newContent);
} catch (InvalidChangeOperationException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index df4c83e..89b265f 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -27,6 +27,7 @@
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
@@ -34,6 +35,7 @@
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevertSubmissionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -185,6 +187,22 @@
}
List<ChangeData> changeDatas = queryProvider.get().bySubmissionId(submissionId);
+ checkPermissionsForAllChanges(changeResource, changeDatas);
+ input.topic = createTopic(input.topic, submissionId);
+ return Response.ok(revertSubmission(changeDatas, input));
+ }
+
+ private String createTopic(String topic, String submissionId) {
+ if (topic == null) {
+ return String.format(
+ "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
+ }
+ return topic;
+ }
+
+ private void checkPermissionsForAllChanges(
+ ChangeResource changeResource, List<ChangeData> changeDatas)
+ throws IOException, AuthException, PermissionBackendException, ResourceConflictException {
for (ChangeData changeData : changeDatas) {
Change change = changeData.change();
@@ -203,23 +221,122 @@
"current patch set %s of change %s not found",
change.currentPatchSetId(), change.currentPatchSetId()));
}
-
- if (input.topic == null) {
- input.topic =
- String.format(
- "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
- }
-
- return Response.ok(revertSubmission(changeDatas, input));
}
private RevertSubmissionInfo revertSubmission(
List<ChangeData> changeData, RevertInput revertInput)
throws RestApiException, IOException, UpdateException, PermissionBackendException,
NoSuchProjectException, ConfigInvalidException, StorageException {
+
Multimap<BranchNameKey, ChangeData> changesPerProjectAndBranch = ArrayListMultimap.create();
changeData.stream().forEach(c -> changesPerProjectAndBranch.put(c.change().getDest(), c));
+ cherryPickInput = createCherryPickInput(revertInput);
+ for (BranchNameKey projectAndBranch : changesPerProjectAndBranch.keySet()) {
+ cherryPickInput.base = null;
+ Project.NameKey project = projectAndBranch.project();
+ cherryPickInput.destination = projectAndBranch.branch();
+ Collection<ChangeData> changesInProjectAndBranch =
+ changesPerProjectAndBranch.get(projectAndBranch);
+
+ // Sort the changes topologically.
+ Iterator<PatchSetData> sortedChangesInProjectAndBranch =
+ sorter.sort(changesInProjectAndBranch).iterator();
+
+ Set<ObjectId> commitIdsInProjectAndBranch =
+ changesInProjectAndBranch.stream()
+ .map(c -> c.currentPatchSet().commitId())
+ .collect(Collectors.toSet());
+
+ revertAllChangesInProjectAndBranch(
+ revertInput, project, sortedChangesInProjectAndBranch, commitIdsInProjectAndBranch);
+ }
+ results.sort(Comparator.comparing(c -> c.revertOf));
+ RevertSubmissionInfo revertSubmissionInfo = new RevertSubmissionInfo();
+ revertSubmissionInfo.revertChanges = results;
+ return revertSubmissionInfo;
+ }
+
+ private void revertAllChangesInProjectAndBranch(
+ RevertInput revertInput,
+ NameKey project,
+ Iterator<PatchSetData> sortedChangesInProjectAndBranch,
+ Set<ObjectId> commitIdsInProjectAndBranch)
+ throws IOException, RestApiException, UpdateException, PermissionBackendException,
+ NoSuchProjectException, ConfigInvalidException {
+
+ String groupName = null;
+ while (sortedChangesInProjectAndBranch.hasNext()) {
+ ChangeNotes changeNotes = sortedChangesInProjectAndBranch.next().data().notes();
+ if (cherryPickInput.base == null) {
+ cherryPickInput.base = getBase(changeNotes, commitIdsInProjectAndBranch).name();
+ }
+
+ revertInput.message = getMessage(revertInput, changeNotes);
+ if (cherryPickInput.base.equals(changeNotes.getCurrentPatchSet().commitId().getName())) {
+ // This is the code in case this is the first revert of this project + branch, and the
+ // revert would be on top of the change being reverted.
+ craeteNormalRevert(revertInput, changeNotes);
+ groupName = cherryPickInput.base;
+ } else {
+ // This is the code in case this is the second revert (or more) of this project + branch.
+ if (groupName == null) {
+ groupName = cherryPickInput.base;
+ }
+ createCherryPickedRevert(revertInput, project, groupName, changeNotes);
+ }
+ }
+ }
+
+ private void createCherryPickedRevert(
+ RevertInput revertInput, NameKey project, String groupName, ChangeNotes changeNotes)
+ throws IOException, ConfigInvalidException, UpdateException, RestApiException {
+ ObjectId revCommitId =
+ commitUtil.createRevertCommit(revertInput.message, changeNotes, user.get());
+ // TODO (paiking): As a future change, the revert should just be done directly on the
+ // target rather than just creating a commit and then cherry-picking it.
+ cherryPickInput.message = revertInput.message;
+ ObjectId generatedChangeId = Change.generateChangeId();
+ Change.Id cherryPickRevertChangeId = Change.id(seq.nextChangeId());
+ // TODO (paiking): In the the future, the timestamp should be the same for all the revert
+ // changes.
+ try (BatchUpdate bu = updateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.setNotify(
+ notifyResolver.resolve(
+ firstNonNull(cherryPickInput.notify, NotifyHandling.ALL),
+ cherryPickInput.notifyDetails));
+ bu.addOp(
+ changeNotes.getChange().getId(),
+ new CreateCherryPickOp(
+ revCommitId,
+ revertInput.topic,
+ generatedChangeId,
+ cherryPickRevertChangeId,
+ groupName));
+ bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
+ bu.addOp(
+ cherryPickRevertChangeId,
+ new NotifyOp(changeNotes.getChange(), cherryPickRevertChangeId));
+
+ bu.execute();
+ }
+ }
+
+ private void craeteNormalRevert(RevertInput revertInput, ChangeNotes changeNotes)
+ throws IOException, RestApiException, UpdateException, PermissionBackendException,
+ NoSuchProjectException, ConfigInvalidException {
+ ChangeInfo revertChangeInfo =
+ revert.apply(changeResourceFactory.create(changeNotes, user.get()), revertInput).value();
+ results.add(revertChangeInfo);
+ cherryPickInput.base =
+ changeNotesFactory
+ .createChecked(Change.id(revertChangeInfo._number))
+ .getCurrentPatchSet()
+ .commitId()
+ .getName();
+ }
+
+ private CherryPickInput createCherryPickInput(RevertInput revertInput) {
cherryPickInput = new CherryPickInput();
// To create a revert change, we create a revert commit that is then cherry-picked. The revert
// change is created for the cherry-picked commit. Notifications are sent only for this change,
@@ -228,91 +345,14 @@
cherryPickInput.notifyDetails = revertInput.notifyDetails;
cherryPickInput.parent = 1;
cherryPickInput.keepReviewers = true;
-
- for (BranchNameKey projectAndBranch : changesPerProjectAndBranch.keySet()) {
- String groupName = null;
- Project.NameKey project = projectAndBranch.project();
- cherryPickInput.destination = projectAndBranch.branch();
- Collection<ChangeData> changesInProjectAndBranch =
- changesPerProjectAndBranch.get(projectAndBranch);
-
- // Sort the changes topologically.
- Iterator<PatchSetData> sortedChangesInProject =
- sorter.sort(changesInProjectAndBranch).iterator();
-
- Set<ObjectId> commitIdsInProjectAndBranch =
- changesInProjectAndBranch.stream()
- .map(c -> c.currentPatchSet().commitId())
- .collect(Collectors.toSet());
-
- while (sortedChangesInProject.hasNext()) {
- ChangeNotes changeNotes = sortedChangesInProject.next().data().notes();
- if (cherryPickInput.base == null) {
- cherryPickInput.base = getBase(changeNotes, commitIdsInProjectAndBranch).name();
- }
-
- revertInput.message = getMessage(revertInput, changeNotes);
- // This is the code in case this is the first revert of this project + branch, and the
- // revert would be on top of the change being reverted.
- if (cherryPickInput.base.equals(changeNotes.getCurrentPatchSet().commitId().getName())) {
- ChangeInfo revertChangeInfo =
- revert
- .apply(changeResourceFactory.create(changeNotes, user.get()), revertInput)
- .value();
- results.add(revertChangeInfo);
- cherryPickInput.base =
- changeNotesFactory
- .createChecked(Change.id(revertChangeInfo._number))
- .getCurrentPatchSet()
- .commitId()
- .getName();
- groupName = cherryPickInput.base;
- } else {
- // This is the code in case this is the second revert (or more) of this project + branch.
- ObjectId revCommitId =
- commitUtil.createRevertCommit(revertInput.message, changeNotes, user.get());
- // TODO (paiking): As a future change, the revert should just be done directly on the
- // target rather than just creating a commit and then cherry-picking it.
- cherryPickInput.message = revertInput.message;
- ObjectId generatedChangeId = Change.generateChangeId();
- Change.Id cherryPickRevertChangeId = Change.id(seq.nextChangeId());
- if (groupName == null) {
- groupName = cherryPickInput.base;
- }
- // TODO (paiking): In the the future, the timestamp should be the same for all the revert
- // changes.
- try (BatchUpdate bu = updateFactory.create(project, user.get(), TimeUtil.nowTs())) {
- bu.setNotify(
- notifyResolver.resolve(
- firstNonNull(cherryPickInput.notify, NotifyHandling.ALL),
- cherryPickInput.notifyDetails));
- bu.addOp(
- changeNotes.getChange().getId(),
- new CreateCherryPickOp(
- revCommitId,
- revertInput.topic,
- generatedChangeId,
- cherryPickRevertChangeId,
- groupName));
- bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
- bu.addOp(
- cherryPickRevertChangeId,
- new NotifyOp(changeNotes.getChange(), cherryPickRevertChangeId));
-
- bu.execute();
- }
- }
- }
- cherryPickInput.base = null;
- }
- results.sort(Comparator.comparing(c -> c.revertOf));
- RevertSubmissionInfo revertSubmissionInfo = new RevertSubmissionInfo();
- revertSubmissionInfo.revertChanges = results;
- return revertSubmissionInfo;
+ return cherryPickInput;
}
private String getMessage(RevertInput revertInput, ChangeNotes changeNotes) {
String subject = changeNotes.getChange().getSubject();
+ if (subject.length() > 63) {
+ subject = subject.substring(0, 59) + "...";
+ }
if (revertInput.message == null) {
return MessageFormat.format(
ChangeMessages.get().revertChangeDefaultMessage,
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 64c4578..79ae43b 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -16,7 +16,6 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;
@@ -459,11 +458,17 @@
try {
ChangeSet indexBackedChangeSet =
mergeSuperSet.setMergeOpRepoManager(orm).completeChangeSet(change, caller);
- checkState(
- indexBackedChangeSet.ids().contains(change.getId()),
- "change %s missing from %s",
- change.getId(),
- indexBackedChangeSet);
+ if (!indexBackedChangeSet.ids().contains(change.getId())) {
+ // indexBackedChangeSet contains only open changes, if the change is missing in this set
+ // it might be that the change was concurrently submitted in the meantime.
+ change = changeDataFactory.create(change).reloadChange();
+ if (!change.isNew()) {
+ throw new ResourceConflictException("change is " + ChangeUtil.status(change));
+ }
+ throw new IllegalStateException(
+ String.format("change %s missing from %s", change.getId(), indexBackedChangeSet));
+ }
+
if (indexBackedChangeSet.furtherHiddenChanges()) {
throw new AuthException(
"A change to be submitted with " + change.getId() + " is not visible");
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index bb69453..e13cab1 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -481,9 +481,9 @@
if (!traceContext.isTracing()) {
String traceId = "retry-on-failure-" + new RequestId();
traceContext.addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
- opts.onAutoTrace().ifPresent(c -> c.accept(traceId));
logger.atFine().withCause(t).log(
"AutoRetry: %s failed, retry with tracing enabled", actionName);
+ opts.onAutoTrace().ifPresent(c -> c.accept(traceId));
metrics.autoRetryCount.increment(actionType, actionName, cause);
return true;
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
index f1ea319..7703b1c 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -284,6 +284,27 @@
}
@Test
+ public void revertChangeWithLongSubject() throws Exception {
+ String changeTitle =
+ "This change has a very long title and therefore it will be cut to 50 characters when the"
+ + " revert change will revert this change";
+ String result = createChange(changeTitle, "a.txt", "message").getChangeId();
+ gApi.changes().id(result).current().review(ReviewInput.approve());
+ gApi.changes().id(result).current().submit();
+ RevertInput revertInput = new RevertInput();
+ ChangeInfo revertChange = gApi.changes().id(result).revert(revertInput).get();
+ assertThat(revertChange.subject)
+ .isEqualTo(String.format("Revert \"%s...\"", changeTitle.substring(0, 59)));
+ assertThat(gApi.changes().id(revertChange.id).current().commit(false).message)
+ .isEqualTo(
+ String.format(
+ "Revert \"%s...\"\n\nThis reverts commit %s.\n\nChange-Id: %s\n",
+ changeTitle.substring(0, 59),
+ gApi.changes().id(result).get().currentRevision,
+ revertChange.changeId));
+ }
+
+ @Test
public void revertNotifications() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes().id(r.getChangeId()).addReviewer(user.email());
@@ -705,6 +726,28 @@
}
@Test
+ public void revertSubmissionRevertsChangeWithLongSubject() throws Exception {
+ String changeTitle =
+ "This change has a very long title and therefore it will be cut to 50 characters when the"
+ + " revert change will revert this change";
+ String result = createChange(changeTitle, "a.txt", "message").getChangeId();
+ gApi.changes().id(result).current().review(ReviewInput.approve());
+ gApi.changes().id(result).current().submit();
+ RevertInput revertInput = new RevertInput();
+ ChangeInfo revertChange =
+ gApi.changes().id(result).revertSubmission(revertInput).revertChanges.get(0);
+ assertThat(revertChange.subject)
+ .isEqualTo(String.format("Revert \"%s...\"", changeTitle.substring(0, 59)));
+ assertThat(gApi.changes().id(revertChange.id).current().commit(false).message)
+ .isEqualTo(
+ String.format(
+ "Revert \"%s...\"\n\nThis reverts commit %s.\n\nChange-Id: %s\n",
+ changeTitle.substring(0, 59),
+ gApi.changes().id(result).get().currentRevision,
+ revertChange.changeId));
+ }
+
+ @Test
@GerritConfig(name = "change.submitWholeTopic", value = "true")
public void revertSubmissionDifferentRepositoriesWithDependantChange() throws Exception {
projectOperations.newProject().name("secondProject").create();
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index cc8d233..bf93d58 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -45,6 +45,7 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.FileContentInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -64,7 +65,6 @@
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.restapi.change.ChangeEdits.EditMessage;
import com.google.gerrit.server.restapi.change.ChangeEdits.Post;
-import com.google.gerrit.server.restapi.change.ChangeEdits.Put;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.inject.Inject;
@@ -501,7 +501,7 @@
@Test
public void createAndChangeEditInOneRequestRest() throws Exception {
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
ensureSameBytes(getFileContentOfEdit(changeId, FILE_NAME), CONTENT_NEW);
@@ -513,7 +513,7 @@
@Test
public void changeEditRest() throws Exception {
createEmptyEditFor(changeId);
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
ensureSameBytes(getFileContentOfEdit(changeId, FILE_NAME), CONTENT_NEW);
@@ -534,7 +534,7 @@
@Test
public void getFileContentRest() throws Exception {
- Put.Input in = new Put.Input();
+ FileContentInput in = new FileContentInput();
in.content = RawInputUtil.create(CONTENT_NEW);
adminRestSession.putRaw(urlEditFile(changeId, FILE_NAME), in.content).assertNoContent();
gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW2));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index c692a3b..c67a842 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -51,7 +51,7 @@
case V6_7:
return "blacktop/elasticsearch:6.7.2";
case V6_8:
- return "blacktop/elasticsearch:6.8.5";
+ return "blacktop/elasticsearch:6.8.6";
case V7_0:
return "blacktop/elasticsearch:7.0.1";
case V7_1:
@@ -63,7 +63,7 @@
case V7_4:
return "blacktop/elasticsearch:7.4.2";
case V7_5:
- return "blacktop/elasticsearch:7.5.0";
+ return "blacktop/elasticsearch:7.5.1";
}
throw new IllegalStateException("No tests for version: " + version.name());
}
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index 46e7d32..403d9d5 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -398,4 +398,4 @@
assert.equal(element._registerText, 'Sign up');
});
});
- </script>
\ No newline at end of file
+ </script>
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index e16c0a0..fa8008c 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -102,8 +102,8 @@
# and httpasyncclient as necessary.
maven_jar(
name = "elasticsearch-rest-client",
- artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.0",
- sha1 = "62535b6fc3a4e943e88e7640eac22e29f03a696d",
+ artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.1",
+ sha1 = "094c155906dc94146fc5adc344ea2c676d487cf2",
)
maven_jar(