Merge "Support to delete a comment with 'NoteDbRewriter' in BatchUpdate"
This commit is contained in:
@@ -4425,6 +4425,62 @@ describes the published comment.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[delete-comment]]
|
||||||
|
=== Delete Comment
|
||||||
|
--
|
||||||
|
'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/link:#comment-id[\{comment-id\}]' +
|
||||||
|
'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/link:#comment-id[\{comment-id\}]/delete'
|
||||||
|
--
|
||||||
|
|
||||||
|
Deletes a published comment of a revision. Instead of deleting the
|
||||||
|
whole comment, this endpoint just replaces the comment's message
|
||||||
|
with a new message, which contains the name of the user who deletes
|
||||||
|
the comment and the reason why it's deleted. The reason can be
|
||||||
|
provided in the request body as a
|
||||||
|
link:#delete-comment-input[DeleteCommentInput] entity.
|
||||||
|
|
||||||
|
Note that only users with the
|
||||||
|
link:access-control.html#capability_administrateServer[Administrate Server]
|
||||||
|
global capability are permitted to delete a comment.
|
||||||
|
|
||||||
|
Please note that some proxies prohibit request bodies for DELETE
|
||||||
|
requests. In this case, if you want to specify options, use a
|
||||||
|
POST request:
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/comments/TvcXrmjM/delete HTTP/1.0
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"reason": "contains confidential information"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
As response a link:#comment-info[CommentInfo] entity is returned that
|
||||||
|
describes the updated comment.
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
)]}'
|
||||||
|
{
|
||||||
|
"id": "TvcXrmjM",
|
||||||
|
"path": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
|
||||||
|
"line": 23,
|
||||||
|
"message": "Comment removed by: Administrator; Reason: contains confidential information",
|
||||||
|
"updated": "2013-02-26 15:40:43.986000000",
|
||||||
|
"author": {
|
||||||
|
"_account_id": 1000096,
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john.doe@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
[[list-robot-comments]]
|
[[list-robot-comments]]
|
||||||
=== List Robot Comments
|
=== List Robot Comments
|
||||||
--
|
--
|
||||||
@@ -5770,6 +5826,20 @@ Links to the commit in external sites as a list of
|
|||||||
link:#web-link-info[WebLinkInfo] entities.
|
link:#web-link-info[WebLinkInfo] entities.
|
||||||
|===========================
|
|===========================
|
||||||
|
|
||||||
|
[[delete-comment-input]]
|
||||||
|
=== DeleteCommentInput
|
||||||
|
The `DeleteCommentInput` entity contains the option for deleting a comment.
|
||||||
|
|
||||||
|
[options="header",cols="1,^1,5"]
|
||||||
|
|=============================
|
||||||
|
|Field Name ||Description
|
||||||
|
|`reason` |optional|
|
||||||
|
The reason why the comment should be deleted. +
|
||||||
|
If set, the comment's message will be replaced with
|
||||||
|
"Comment removed by: `name`; Reason: `reason`",
|
||||||
|
or just "Comment removed by: `name`." if not set.
|
||||||
|
|=============================
|
||||||
|
|
||||||
[[delete-reviewer-input]]
|
[[delete-reviewer-input]]
|
||||||
=== DeleteReviewerInput
|
=== DeleteReviewerInput
|
||||||
The `DeleteReviewerInput` entity contains options for the deletion of a
|
The `DeleteReviewerInput` entity contains options for the deletion of a
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
|
|||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
|
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
|
||||||
import com.google.gerrit.acceptance.NoHttpd;
|
import com.google.gerrit.acceptance.NoHttpd;
|
||||||
import com.google.gerrit.acceptance.PushOneCommit;
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
|
||||||
import com.google.gerrit.extensions.api.changes.DraftInput;
|
import com.google.gerrit.extensions.api.changes.DraftInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
||||||
@@ -34,27 +36,41 @@ import com.google.gerrit.extensions.client.Comment;
|
|||||||
import com.google.gerrit.extensions.client.Side;
|
import com.google.gerrit.extensions.client.Side;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
import com.google.gerrit.extensions.restapi.IdString;
|
import com.google.gerrit.extensions.restapi.IdString;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.extensions.restapi.TopLevelResource;
|
import com.google.gerrit.extensions.restapi.TopLevelResource;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.Patch;
|
import com.google.gerrit.reviewdb.client.Patch;
|
||||||
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
import com.google.gerrit.server.change.ChangesCollection;
|
import com.google.gerrit.server.change.ChangesCollection;
|
||||||
import com.google.gerrit.server.change.PostReview;
|
import com.google.gerrit.server.change.PostReview;
|
||||||
import com.google.gerrit.server.change.RevisionResource;
|
import com.google.gerrit.server.change.RevisionResource;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNoteUtil;
|
||||||
|
import com.google.gerrit.server.notedb.DeleteCommentRewriter;
|
||||||
import com.google.gerrit.testutil.FakeEmailSender;
|
import com.google.gerrit.testutil.FakeEmailSender;
|
||||||
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
import java.io.IOException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -67,6 +83,8 @@ public class CommentsIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Inject private FakeEmailSender email;
|
@Inject private FakeEmailSender email;
|
||||||
|
|
||||||
|
@Inject private ChangeNoteUtil noteUtil;
|
||||||
|
|
||||||
private final Integer[] lines = {0, 1};
|
private final Integer[] lines = {0, 1};
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -739,6 +757,168 @@ public class CommentsIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteCommentCannotBeAppliedByUser() throws Exception {
|
||||||
|
PushOneCommit.Result result = createChange();
|
||||||
|
CommentInput targetComment = addComment(result.getChangeId(), "My password: abc123");
|
||||||
|
|
||||||
|
Map<String, List<CommentInfo>> commentsMap =
|
||||||
|
getPublishedComments(result.getChangeId(), result.getCommit().name());
|
||||||
|
|
||||||
|
assertThat(commentsMap.size()).isEqualTo(1);
|
||||||
|
assertThat(commentsMap.get(FILE_NAME)).hasSize(1);
|
||||||
|
|
||||||
|
String uuid = commentsMap.get(targetComment.path).get(0).id;
|
||||||
|
DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
exception.expect(AuthException.class);
|
||||||
|
gApi.changes().id(result.getChangeId()).current().comment(uuid).delete(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteCommentByRewritingCommitHistory() throws Exception {
|
||||||
|
// Create change (the 1st commit on the change's meta branch).
|
||||||
|
PushOneCommit.Result result = createChange();
|
||||||
|
String changeId = result.getChangeId();
|
||||||
|
Change.Id id = result.getChange().getId();
|
||||||
|
|
||||||
|
// Add two comments in patch set 1 (the 2nd commit on the change's meta branch).
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
CommentInput comment1 = newComment(FILE_NAME, Side.REVISION, 0, "My password: abc123", false);
|
||||||
|
CommentInput comment2 = newComment(FILE_NAME, Side.REVISION, 1, "nit: long line", false);
|
||||||
|
reviewInput.comments = ImmutableMap.of(FILE_NAME, Lists.newArrayList(comment1, comment2));
|
||||||
|
reviewInput.label("Code-Review", 1);
|
||||||
|
gApi.changes().id(changeId).current().review(reviewInput);
|
||||||
|
|
||||||
|
// Create patch set 2 (the 3rd commit on the change's meta branch).
|
||||||
|
amendChange(changeId);
|
||||||
|
|
||||||
|
// Add 'comment3' in patch set 2 (the 4th commit on the change's meta branch).
|
||||||
|
CommentInput comment3 = addComment(changeId, "typo");
|
||||||
|
|
||||||
|
Map<String, List<CommentInfo>> commentsMap = getPublishedComments(changeId);
|
||||||
|
assertThat(commentsMap).hasSize(1);
|
||||||
|
assertThat(commentsMap.get(FILE_NAME)).hasSize(3);
|
||||||
|
Optional<CommentInfo> targetCommentInfo =
|
||||||
|
commentsMap
|
||||||
|
.get(FILE_NAME)
|
||||||
|
.stream()
|
||||||
|
.filter(c -> c.message.equals("My password: abc123"))
|
||||||
|
.findFirst();
|
||||||
|
assertThat(targetCommentInfo.isPresent()).isTrue();
|
||||||
|
|
||||||
|
List<RevCommit> commitsBeforeDelete = new ArrayList<>();
|
||||||
|
if (notesMigration.commitChangeWrites()) {
|
||||||
|
commitsBeforeDelete = getCommits(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
String uuid = targetCommentInfo.get().id;
|
||||||
|
// Get the target comment.
|
||||||
|
CommentInfo oldComment =
|
||||||
|
gApi.changes().id(changeId).revision(result.getCommit().getName()).comment(uuid).get();
|
||||||
|
|
||||||
|
// Delete the target comment.
|
||||||
|
DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
|
||||||
|
setApiUser(admin);
|
||||||
|
CommentInfo updatedComment =
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.revision(result.getCommit().getName())
|
||||||
|
.comment(uuid)
|
||||||
|
.delete(input);
|
||||||
|
|
||||||
|
String expectedMsg =
|
||||||
|
String.format("Comment removed by: %s; Reason: %s", admin.fullName, input.reason);
|
||||||
|
|
||||||
|
assertThat(updatedComment.message).isEqualTo(expectedMsg);
|
||||||
|
updatedComment.message = oldComment.message;
|
||||||
|
assertThat(updatedComment).isEqualTo(oldComment);
|
||||||
|
|
||||||
|
// Check the comment's message has been replaced in NoteDb.
|
||||||
|
if (notesMigration.commitChangeWrites()) {
|
||||||
|
assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that comments can still be added correctly.
|
||||||
|
CommentInput comment4 = addComment(changeId, "too much space");
|
||||||
|
commentsMap = getPublishedComments(changeId);
|
||||||
|
|
||||||
|
assertThat(commentsMap).hasSize(1);
|
||||||
|
List<CommentInput> comments =
|
||||||
|
Lists.transform(commentsMap.get(FILE_NAME), infoToInput(FILE_NAME));
|
||||||
|
|
||||||
|
// Change comment1's message to the expected message.
|
||||||
|
comment1.message = expectedMsg;
|
||||||
|
assertThat(comments).containsExactly(comment1, comment2, comment3, comment4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommentInput addComment(String changeId, String message) throws Exception {
|
||||||
|
ReviewInput input = new ReviewInput();
|
||||||
|
CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, message, false);
|
||||||
|
input.comments = ImmutableMap.of(comment.path, Lists.newArrayList(comment));
|
||||||
|
gApi.changes().id(changeId).current().review(input);
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RevCommit> getCommits(Change.Id changeId)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
try (Repository repo = repoManager.openRepository(project);
|
||||||
|
RevWalk revWalk = new RevWalk(repo)) {
|
||||||
|
Ref metaRef = repo.exactRef(RefNames.changeMetaRef(changeId));
|
||||||
|
revWalk.markStart(revWalk.parseCommit(metaRef.getObjectId()));
|
||||||
|
return Lists.newArrayList(revWalk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the commits, which contain the target comment before, should still contain the comment with
|
||||||
|
* the updated message. All the other metas of the commits should be exactly the same.
|
||||||
|
*/
|
||||||
|
private void assertMetaBranchCommitsAfterRewriting(
|
||||||
|
List<RevCommit> beforeDelete,
|
||||||
|
Change.Id changeId,
|
||||||
|
String targetCommentUuid,
|
||||||
|
String expectedMessage)
|
||||||
|
throws Exception {
|
||||||
|
List<RevCommit> afterDelete = getCommits(changeId);
|
||||||
|
assertThat(afterDelete).hasSize(beforeDelete.size());
|
||||||
|
|
||||||
|
try (Repository repo = repoManager.openRepository(project);
|
||||||
|
ObjectReader reader = repo.newObjectReader()) {
|
||||||
|
for (int i = 0; i < beforeDelete.size(); i++) {
|
||||||
|
RevCommit commitBefore = beforeDelete.get(i);
|
||||||
|
RevCommit commitAfter = afterDelete.get(i);
|
||||||
|
|
||||||
|
Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapBefore =
|
||||||
|
DeleteCommentRewriter.getPublishedComments(
|
||||||
|
noteUtil, changeId, reader, NoteMap.read(reader, commitBefore));
|
||||||
|
Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapAfter =
|
||||||
|
DeleteCommentRewriter.getPublishedComments(
|
||||||
|
noteUtil, changeId, reader, NoteMap.read(reader, commitAfter));
|
||||||
|
|
||||||
|
if (commentMapBefore.containsKey(targetCommentUuid)) {
|
||||||
|
assertThat(commentMapAfter).containsKey(targetCommentUuid);
|
||||||
|
com.google.gerrit.reviewdb.client.Comment comment =
|
||||||
|
commentMapAfter.get(targetCommentUuid);
|
||||||
|
assertThat(comment.message).isEqualTo(expectedMessage);
|
||||||
|
comment.message = commentMapBefore.get(targetCommentUuid).message;
|
||||||
|
commentMapAfter.put(targetCommentUuid, comment);
|
||||||
|
assertThat(commentMapAfter).isEqualTo(commentMapBefore);
|
||||||
|
} else {
|
||||||
|
assertThat(commentMapAfter).doesNotContainKey(targetCommentUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other metas should be exactly the same.
|
||||||
|
assertThat(commitAfter.getFullMessage()).isEqualTo(commitBefore.getFullMessage());
|
||||||
|
assertThat(commitAfter.getCommitterIdent()).isEqualTo(commitBefore.getCommitterIdent());
|
||||||
|
assertThat(commitAfter.getAuthorIdent()).isEqualTo(commitBefore.getAuthorIdent());
|
||||||
|
assertThat(commitAfter.getEncoding()).isEqualTo(commitBefore.getEncoding());
|
||||||
|
assertThat(commitAfter.getEncodingName()).isEqualTo(commitBefore.getEncodingName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String extractComments(String msg) {
|
private static String extractComments(String msg) {
|
||||||
// Extract lines between start "....." and end "-- ".
|
// Extract lines between start "....." and end "-- ".
|
||||||
Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL);
|
Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL);
|
||||||
@@ -803,6 +983,10 @@ public class CommentsIT extends AbstractDaemonTest {
|
|||||||
return gApi.changes().id(changeId).revision(revId).drafts();
|
return gApi.changes().id(changeId).revision(revId).drafts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, List<CommentInfo>> getPublishedComments(String changeId) throws Exception {
|
||||||
|
return gApi.changes().id(changeId).comments();
|
||||||
|
}
|
||||||
|
|
||||||
private CommentInfo getDraftComment(String changeId, String revId, String uuid) throws Exception {
|
private CommentInfo getDraftComment(String changeId, String revId, String uuid) throws Exception {
|
||||||
return gApi.changes().id(changeId).revision(revId).draft(uuid).get();
|
return gApi.changes().id(changeId).revision(revId).draft(uuid).get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ import com.google.gerrit.extensions.restapi.RestApiException;
|
|||||||
public interface CommentApi {
|
public interface CommentApi {
|
||||||
CommentInfo get() throws RestApiException;
|
CommentInfo get() throws RestApiException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a published comment of a revision. For NoteDb, it deletes the comment by rewriting the
|
||||||
|
* commit history.
|
||||||
|
*
|
||||||
|
* <p>Note instead of deleting the whole comment, this endpoint just replaces the comment's
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* @return the comment with its message updated.
|
||||||
|
*/
|
||||||
|
CommentInfo delete(DeleteCommentInput input) throws RestApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default implementation which allows source compatibility when adding new methods to the
|
* A default implementation which allows source compatibility when adding new methods to the
|
||||||
* interface.
|
* interface.
|
||||||
@@ -30,5 +41,10 @@ public interface CommentApi {
|
|||||||
public CommentInfo get() {
|
public CommentInfo get() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentInfo delete(DeleteCommentInput input) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (C) 2017 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.common.base.Strings;
|
||||||
|
import com.google.gerrit.extensions.restapi.DefaultInput;
|
||||||
|
|
||||||
|
public class DeleteCommentInput {
|
||||||
|
@DefaultInput public String reason;
|
||||||
|
|
||||||
|
public DeleteCommentInput() {
|
||||||
|
reason = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeleteCommentInput(String reason) {
|
||||||
|
this.reason = Strings.nullToEmpty(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server;
|
|||||||
|
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static java.util.stream.Collectors.toSet;
|
import static java.util.stream.Collectors.toSet;
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ import com.google.gerrit.server.config.GerritServerId;
|
|||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
|
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
||||||
import com.google.gerrit.server.notedb.NotesMigration;
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
import com.google.gerrit.server.patch.PatchListCache;
|
import com.google.gerrit.server.patch.PatchListCache;
|
||||||
import com.google.gerrit.server.patch.PatchListNotAvailableException;
|
import com.google.gerrit.server.patch.PatchListNotAvailableException;
|
||||||
@@ -223,8 +225,7 @@ public class CommentsUtil {
|
|||||||
|
|
||||||
public List<Comment> publishedByChange(ReviewDb db, ChangeNotes notes) throws OrmException {
|
public List<Comment> publishedByChange(ReviewDb db, ChangeNotes notes) throws OrmException {
|
||||||
if (!migration.readChanges()) {
|
if (!migration.readChanges()) {
|
||||||
return sort(
|
return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), PUBLISHED));
|
||||||
byCommentStatus(db.patchComments().byChange(notes.getChangeId()), Status.PUBLISHED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notes.load();
|
notes.load();
|
||||||
@@ -403,6 +404,25 @@ public class CommentsUtil {
|
|||||||
.delete(toPatchLineComments(update.getId(), PatchLineComment.Status.DRAFT, comments));
|
.delete(toPatchLineComments(update.getId(), PatchLineComment.Status.DRAFT, comments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteCommentByRewritingHistory(
|
||||||
|
ReviewDb db, ChangeUpdate update, Comment.Key commentKey, PatchSet.Id psId, String newMessage)
|
||||||
|
throws OrmException {
|
||||||
|
if (PrimaryStorage.of(update.getChange()).equals(PrimaryStorage.REVIEW_DB)) {
|
||||||
|
PatchLineComment.Key key =
|
||||||
|
new PatchLineComment.Key(new Patch.Key(psId, commentKey.filename), commentKey.uuid);
|
||||||
|
PatchLineComment patchLineComment = db.patchComments().get(key);
|
||||||
|
|
||||||
|
if (!patchLineComment.getStatus().equals(PUBLISHED)) {
|
||||||
|
throw new OrmException(String.format("comment %s is not published", key));
|
||||||
|
}
|
||||||
|
|
||||||
|
patchLineComment.setMessage(newMessage);
|
||||||
|
db.patchComments().upsert(Collections.singleton(patchLineComment));
|
||||||
|
}
|
||||||
|
|
||||||
|
update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteAllDraftsFromAllUsers(Change.Id changeId) throws IOException {
|
public void deleteAllDraftsFromAllUsers(Change.Id changeId) throws IOException {
|
||||||
try (Repository repo = repoManager.openRepository(allUsers);
|
try (Repository repo = repoManager.openRepository(allUsers);
|
||||||
RevWalk rw = new RevWalk(repo)) {
|
RevWalk rw = new RevWalk(repo)) {
|
||||||
@@ -533,7 +553,7 @@ public class CommentsUtil {
|
|||||||
ctx.getUser().updateRealAccountId(d::setRealAuthor);
|
ctx.getUser().updateRealAccountId(d::setRealAuthor);
|
||||||
setCommentRevId(d, patchListCache, notes.getChange(), ps);
|
setCommentRevId(d, patchListCache, notes.getChange(), ps);
|
||||||
}
|
}
|
||||||
putComments(ctx.getDb(), ctx.getUpdate(psId), PatchLineComment.Status.PUBLISHED, drafts);
|
putComments(ctx.getDb(), ctx.getUpdate(psId), PUBLISHED, drafts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
|
private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ package com.google.gerrit.server.api.changes;
|
|||||||
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
|
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.api.changes.CommentApi;
|
import com.google.gerrit.extensions.api.changes.CommentApi;
|
||||||
|
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.server.change.CommentResource;
|
import com.google.gerrit.server.change.CommentResource;
|
||||||
|
import com.google.gerrit.server.change.DeleteComment;
|
||||||
import com.google.gerrit.server.change.GetComment;
|
import com.google.gerrit.server.change.GetComment;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
@@ -30,11 +32,14 @@ class CommentApiImpl implements CommentApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final GetComment getComment;
|
private final GetComment getComment;
|
||||||
|
private final DeleteComment deleteComment;
|
||||||
private final CommentResource comment;
|
private final CommentResource comment;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CommentApiImpl(GetComment getComment, @Assisted CommentResource comment) {
|
CommentApiImpl(
|
||||||
|
GetComment getComment, DeleteComment deleteComment, @Assisted CommentResource comment) {
|
||||||
this.getComment = getComment;
|
this.getComment = getComment;
|
||||||
|
this.deleteComment = deleteComment;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,4 +51,13 @@ class CommentApiImpl implements CommentApi {
|
|||||||
throw asRestApiException("Cannot retrieve comment", e);
|
throw asRestApiException("Cannot retrieve comment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentInfo delete(DeleteCommentInput input) throws RestApiException {
|
||||||
|
try {
|
||||||
|
return deleteComment.apply(comment, input);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw asRestApiException("Cannot delete comment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ package com.google.gerrit.server.api.changes;
|
|||||||
|
|
||||||
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
|
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
|
||||||
import com.google.gerrit.extensions.api.changes.DraftApi;
|
import com.google.gerrit.extensions.api.changes.DraftApi;
|
||||||
import com.google.gerrit.extensions.api.changes.DraftInput;
|
import com.google.gerrit.extensions.api.changes.DraftInput;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.NotImplementedException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.server.change.DeleteDraftComment;
|
import com.google.gerrit.server.change.DeleteDraftComment;
|
||||||
import com.google.gerrit.server.change.DraftCommentResource;
|
import com.google.gerrit.server.change.DraftCommentResource;
|
||||||
@@ -75,4 +77,9 @@ class DraftApiImpl implements DraftApi {
|
|||||||
throw asRestApiException("Cannot delete draft", e);
|
throw asRestApiException("Cannot delete draft", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentInfo delete(DeleteCommentInput input) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,4 +48,8 @@ public class CommentResource implements RestResource {
|
|||||||
Account.Id getAuthorId() {
|
Account.Id getAuthorId() {
|
||||||
return comment.author.getId();
|
return comment.author.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RevisionResource getRevisionResource() {
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (C) 2017 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.gerrit.common.TimeUtil;
|
||||||
|
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
|
||||||
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.CommentsUtil;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.gerrit.server.patch.PatchListCache;
|
||||||
|
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||||
|
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||||
|
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||||
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
|
import com.google.gerrit.server.update.ChangeContext;
|
||||||
|
import com.google.gerrit.server.update.UpdateException;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DeleteComment implements RestModifyView<CommentResource, DeleteCommentInput> {
|
||||||
|
|
||||||
|
private final Provider<CurrentUser> userProvider;
|
||||||
|
private final Provider<ReviewDb> dbProvider;
|
||||||
|
private final PermissionBackend permissionBackend;
|
||||||
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
|
private final CommentsUtil commentsUtil;
|
||||||
|
private final PatchSetUtil psUtil;
|
||||||
|
private final BatchUpdate.Factory updateFactory;
|
||||||
|
private final PatchListCache patchListCache;
|
||||||
|
private final Provider<CommentJson> commentJson;
|
||||||
|
private final ChangeNotes.Factory notesFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DeleteComment(
|
||||||
|
Provider<CurrentUser> userProvider,
|
||||||
|
Provider<ReviewDb> dbProvider,
|
||||||
|
PermissionBackend permissionBackend,
|
||||||
|
BatchUpdate.Factory batchUpdateFactory,
|
||||||
|
CommentsUtil commentsUtil,
|
||||||
|
PatchSetUtil psUtil,
|
||||||
|
BatchUpdate.Factory updateFactory,
|
||||||
|
PatchListCache patchListCache,
|
||||||
|
Provider<CommentJson> commentJson,
|
||||||
|
ChangeNotes.Factory notesFactory) {
|
||||||
|
this.userProvider = userProvider;
|
||||||
|
this.dbProvider = dbProvider;
|
||||||
|
this.permissionBackend = permissionBackend;
|
||||||
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
|
this.commentsUtil = commentsUtil;
|
||||||
|
this.psUtil = psUtil;
|
||||||
|
this.updateFactory = updateFactory;
|
||||||
|
this.patchListCache = patchListCache;
|
||||||
|
this.commentJson = commentJson;
|
||||||
|
this.notesFactory = notesFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentInfo apply(CommentResource rsrc, DeleteCommentInput input)
|
||||||
|
throws RestApiException, IOException, ConfigInvalidException, OrmException,
|
||||||
|
PermissionBackendException, UpdateException {
|
||||||
|
CurrentUser user = userProvider.get();
|
||||||
|
permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
|
||||||
|
|
||||||
|
String newMessage = getCommentNewMessage(user.asIdentifiedUser().getName(), input.reason);
|
||||||
|
DeleteCommentOp deleteCommentOp = new DeleteCommentOp(rsrc, newMessage);
|
||||||
|
try (BatchUpdate batchUpdate =
|
||||||
|
batchUpdateFactory.create(
|
||||||
|
dbProvider.get(), rsrc.getRevisionResource().getProject(), user, TimeUtil.nowTs())) {
|
||||||
|
batchUpdate.addOp(rsrc.getRevisionResource().getChange().getId(), deleteCommentOp).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeNotes updatedNotes =
|
||||||
|
notesFactory.createChecked(rsrc.getRevisionResource().getChange().getId());
|
||||||
|
List<Comment> changeComments = commentsUtil.publishedByChange(dbProvider.get(), updatedNotes);
|
||||||
|
Optional<Comment> updatedComment =
|
||||||
|
changeComments.stream().filter(c -> c.key.equals(rsrc.getComment().key)).findFirst();
|
||||||
|
if (!updatedComment.isPresent()) {
|
||||||
|
// This should not happen as this endpoint should not remove the whole comment.
|
||||||
|
throw new ResourceNotFoundException("comment not found: " + rsrc.getComment().key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentJson.get().newCommentFormatter().format(updatedComment.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCommentNewMessage(String name, String reason) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder("Comment removed by: ").append(name);
|
||||||
|
if (!Strings.isNullOrEmpty(reason)) {
|
||||||
|
stringBuilder.append("; Reason: ").append(reason);
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DeleteCommentOp implements BatchUpdateOp {
|
||||||
|
private final CommentResource rsrc;
|
||||||
|
private final String newMessage;
|
||||||
|
|
||||||
|
DeleteCommentOp(CommentResource rsrc, String newMessage) {
|
||||||
|
this.rsrc = rsrc;
|
||||||
|
this.newMessage = newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateChange(ChangeContext ctx)
|
||||||
|
throws ResourceConflictException, OrmException, ResourceNotFoundException {
|
||||||
|
PatchSet.Id psId = ctx.getChange().currentPatchSetId();
|
||||||
|
commentsUtil.deleteCommentByRewritingHistory(
|
||||||
|
ctx.getDb(),
|
||||||
|
ctx.getUpdate(psId),
|
||||||
|
rsrc.getComment().key,
|
||||||
|
rsrc.getPatchSet().getId(),
|
||||||
|
newMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,6 +138,8 @@ public class Module extends RestApiModule {
|
|||||||
|
|
||||||
child(REVISION_KIND, "comments").to(Comments.class);
|
child(REVISION_KIND, "comments").to(Comments.class);
|
||||||
get(COMMENT_KIND).to(GetComment.class);
|
get(COMMENT_KIND).to(GetComment.class);
|
||||||
|
delete(COMMENT_KIND).to(DeleteComment.class);
|
||||||
|
post(COMMENT_KIND, "delete").to(DeleteComment.class);
|
||||||
|
|
||||||
child(REVISION_KIND, "robotcomments").to(RobotComments.class);
|
child(REVISION_KIND, "robotcomments").to(RobotComments.class);
|
||||||
get(ROBOT_COMMENT_KIND).to(GetRobotComment.class);
|
get(ROBOT_COMMENT_KIND).to(GetRobotComment.class);
|
||||||
|
|||||||
@@ -124,9 +124,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final AccountCache accountCache;
|
private final AccountCache accountCache;
|
||||||
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
||||||
private final RobotCommentUpdate.Factory robotCommentUpdateFactory;
|
private final RobotCommentUpdate.Factory robotCommentUpdateFactory;
|
||||||
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
private final DeleteCommentRewriter.Factory deleteCommentRewriterFactory;
|
||||||
|
|
||||||
private final Table<String, Account.Id, Optional<Short>> approvals;
|
private final Table<String, Account.Id, Optional<Short>> approvals;
|
||||||
private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>();
|
private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>();
|
||||||
@@ -158,6 +159,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
|
|
||||||
private ChangeDraftUpdate draftUpdate;
|
private ChangeDraftUpdate draftUpdate;
|
||||||
private RobotCommentUpdate robotCommentUpdate;
|
private RobotCommentUpdate robotCommentUpdate;
|
||||||
|
private DeleteCommentRewriter deleteCommentRewriter;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
private ChangeUpdate(
|
private ChangeUpdate(
|
||||||
@@ -169,6 +171,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
|
DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
ChangeNoteUtil noteUtil) {
|
ChangeNoteUtil noteUtil) {
|
||||||
@@ -181,6 +184,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
updateManagerFactory,
|
updateManagerFactory,
|
||||||
draftUpdateFactory,
|
draftUpdateFactory,
|
||||||
robotCommentUpdateFactory,
|
robotCommentUpdateFactory,
|
||||||
|
deleteCommentRewriterFactory,
|
||||||
projectCache,
|
projectCache,
|
||||||
ctl,
|
ctl,
|
||||||
serverIdent.getWhen(),
|
serverIdent.getWhen(),
|
||||||
@@ -197,6 +201,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
|
DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
@@ -210,6 +215,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
updateManagerFactory,
|
updateManagerFactory,
|
||||||
draftUpdateFactory,
|
draftUpdateFactory,
|
||||||
robotCommentUpdateFactory,
|
robotCommentUpdateFactory,
|
||||||
|
deleteCommentRewriterFactory,
|
||||||
ctl,
|
ctl,
|
||||||
when,
|
when,
|
||||||
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
||||||
@@ -235,15 +241,17 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
|
DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
@Assisted Comparator<String> labelNameComparator,
|
@Assisted Comparator<String> labelNameComparator,
|
||||||
ChangeNoteUtil noteUtil) {
|
ChangeNoteUtil noteUtil) {
|
||||||
super(cfg, migration, ctl, serverIdent, anonymousCowardName, noteUtil, when);
|
super(cfg, migration, ctl, serverIdent, anonymousCowardName, noteUtil, when);
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
this.draftUpdateFactory = draftUpdateFactory;
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
||||||
this.updateManagerFactory = updateManagerFactory;
|
this.deleteCommentRewriterFactory = deleteCommentRewriterFactory;
|
||||||
this.approvals = approvals(labelNameComparator);
|
this.approvals = approvals(labelNameComparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +265,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
|
DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteUtil noteUtil,
|
||||||
@Assisted Change change,
|
@Assisted Change change,
|
||||||
@Assisted("effective") @Nullable Account.Id accountId,
|
@Assisted("effective") @Nullable Account.Id accountId,
|
||||||
@@ -280,6 +289,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
this.draftUpdateFactory = draftUpdateFactory;
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
||||||
this.updateManagerFactory = updateManagerFactory;
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
|
this.deleteCommentRewriterFactory = deleteCommentRewriterFactory;
|
||||||
this.approvals = approvals(labelNameComparator);
|
this.approvals = approvals(labelNameComparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +404,11 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
createDraftUpdateIfNull().deleteComment(c);
|
createDraftUpdateIfNull().deleteComment(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteCommentByRewritingHistory(String uuid, String newMessage) {
|
||||||
|
deleteCommentRewriter =
|
||||||
|
deleteCommentRewriterFactory.create(getChange().getId(), uuid, newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
ChangeDraftUpdate createDraftUpdateIfNull() {
|
ChangeDraftUpdate createDraftUpdateIfNull() {
|
||||||
if (draftUpdate == null) {
|
if (draftUpdate == null) {
|
||||||
@@ -596,6 +611,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
@Override
|
@Override
|
||||||
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins, ObjectId curr)
|
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins, ObjectId curr)
|
||||||
throws OrmException, IOException {
|
throws OrmException, IOException {
|
||||||
|
checkState(deleteCommentRewriter == null, "cannot update and rewrite ref in one BatchUpdate");
|
||||||
|
|
||||||
CommitBuilder cb = new CommitBuilder();
|
CommitBuilder cb = new CommitBuilder();
|
||||||
|
|
||||||
int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
|
int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
|
||||||
@@ -798,6 +815,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
return robotCommentUpdate;
|
return robotCommentUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeleteCommentRewriter getDeleteCommentRewriter() {
|
||||||
|
return deleteCommentRewriter;
|
||||||
|
}
|
||||||
|
|
||||||
public void setAllowWriteToNewRef(boolean allow) {
|
public void setAllowWriteToNewRef(boolean allow) {
|
||||||
isAllowWriteToNewtRef = allow;
|
isAllowWriteToNewtRef = allow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
// Copyright (C) 2017 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevSort;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a published comment from NoteDb by rewriting the commit history. Instead of deleting the
|
||||||
|
* whole comment, it just replaces the comment's message with a new message.
|
||||||
|
*/
|
||||||
|
public class DeleteCommentRewriter implements NoteDbRewriter {
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
/**
|
||||||
|
* Creates a DeleteCommentRewriter instance.
|
||||||
|
*
|
||||||
|
* @param id the id of the change which contains the target comment.
|
||||||
|
* @param uuid the uuid of the target comment.
|
||||||
|
* @param newMessage the message used to replace the old message of the target comment.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
DeleteCommentRewriter create(
|
||||||
|
Change.Id id, @Assisted("uuid") String uuid, @Assisted("newMessage") String newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ChangeNoteUtil noteUtil;
|
||||||
|
private final Change.Id changeId;
|
||||||
|
private final String uuid;
|
||||||
|
private final String newMessage;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DeleteCommentRewriter(
|
||||||
|
ChangeNoteUtil noteUtil,
|
||||||
|
@Assisted Change.Id changeId,
|
||||||
|
@Assisted("uuid") String uuid,
|
||||||
|
@Assisted("newMessage") String newMessage) {
|
||||||
|
this.noteUtil = noteUtil;
|
||||||
|
this.changeId = changeId;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.newMessage = newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRefName() {
|
||||||
|
return RefNames.changeMetaRef(changeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip)
|
||||||
|
throws IOException, ConfigInvalidException, OrmException {
|
||||||
|
checkArgument(!currTip.equals(ObjectId.zeroId()));
|
||||||
|
|
||||||
|
// Walk from the first commit of the branch.
|
||||||
|
revWalk.reset();
|
||||||
|
revWalk.markStart(revWalk.parseCommit(currTip));
|
||||||
|
revWalk.sort(RevSort.REVERSE);
|
||||||
|
|
||||||
|
ObjectReader reader = revWalk.getObjectReader();
|
||||||
|
ObjectId newTip = revWalk.next(); // The first commit will not be rewritten.
|
||||||
|
NoteMap newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
|
||||||
|
Map<String, Comment> parentComments =
|
||||||
|
getPublishedComments(noteUtil, changeId, reader, newTipNoteMap);
|
||||||
|
|
||||||
|
boolean rewrite = false;
|
||||||
|
RevCommit originalCommit;
|
||||||
|
while ((originalCommit = revWalk.next()) != null) {
|
||||||
|
NoteMap noteMap = NoteMap.read(reader, originalCommit);
|
||||||
|
Map<String, Comment> currComments = getPublishedComments(noteUtil, changeId, reader, noteMap);
|
||||||
|
|
||||||
|
if (!rewrite && currComments.containsKey(uuid)) {
|
||||||
|
rewrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rewrite) {
|
||||||
|
parentComments = currComments;
|
||||||
|
newTip = originalCommit;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Comment> putInComments = getPutInComments(parentComments, currComments);
|
||||||
|
List<Comment> deletedComments = getDeletedComments(parentComments, currComments);
|
||||||
|
newTip =
|
||||||
|
rewriteCommit(
|
||||||
|
originalCommit,
|
||||||
|
newTipNoteMap,
|
||||||
|
newTip,
|
||||||
|
inserter,
|
||||||
|
reader,
|
||||||
|
putInComments,
|
||||||
|
deletedComments);
|
||||||
|
newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
|
||||||
|
parentComments = currComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the comments which are presented at a commit. Note they include the comments put in by
|
||||||
|
* the previous commits.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static Map<String, Comment> getPublishedComments(
|
||||||
|
ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
return RevisionNoteMap.parse(noteUtil, changeId, reader, noteMap, PUBLISHED)
|
||||||
|
.revisionNotes
|
||||||
|
.values()
|
||||||
|
.stream()
|
||||||
|
.flatMap(n -> n.getComments().stream())
|
||||||
|
.collect(Collectors.toMap(c -> c.key.uuid, c -> c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the comments put in by the current commit. The message of the target comment will be
|
||||||
|
* replaced by the new message.
|
||||||
|
*
|
||||||
|
* @param parMap the comment map of the parent commit.
|
||||||
|
* @param curMap the comment map of the current commit.
|
||||||
|
* @return The comments put in by the current commit.
|
||||||
|
*/
|
||||||
|
private List<Comment> getPutInComments(Map<String, Comment> parMap, Map<String, Comment> curMap) {
|
||||||
|
List<Comment> comments = new ArrayList<>();
|
||||||
|
for (String key : curMap.keySet()) {
|
||||||
|
if (!parMap.containsKey(key)) {
|
||||||
|
Comment comment = curMap.get(key);
|
||||||
|
if (key.equals(uuid)) {
|
||||||
|
comment.message = newMessage;
|
||||||
|
}
|
||||||
|
comments.add(comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the comments deleted by the current commit.
|
||||||
|
*
|
||||||
|
* @param parMap the comment map of the parent commit.
|
||||||
|
* @param curMap the comment map of the current commit.
|
||||||
|
* @return The comments deleted by the current commit.
|
||||||
|
*/
|
||||||
|
private List<Comment> getDeletedComments(
|
||||||
|
Map<String, Comment> parMap, Map<String, Comment> curMap) {
|
||||||
|
return parMap
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> !curMap.containsKey(c.getKey()))
|
||||||
|
.map(c -> c.getValue())
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites one commit.
|
||||||
|
*
|
||||||
|
* @param originalCommit the original commit to be rewritten.
|
||||||
|
* @param parentNoteMap the {@code NoteMap} of the new commit's parent.
|
||||||
|
* @param parentId the {@code ObjectId} of the new commit's parent.
|
||||||
|
* @param inserter the {@code ObjectInserter} for the rewrite process.
|
||||||
|
* @param reader the {@code ObjectReader} for the rewrite process.
|
||||||
|
* @param putInComments the comments put in by this commit.
|
||||||
|
* @param deletedComments the comments deleted by this commit.
|
||||||
|
* @return the {@code objectId} of the new commit.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ConfigInvalidException
|
||||||
|
*/
|
||||||
|
private ObjectId rewriteCommit(
|
||||||
|
RevCommit originalCommit,
|
||||||
|
NoteMap parentNoteMap,
|
||||||
|
ObjectId parentId,
|
||||||
|
ObjectInserter inserter,
|
||||||
|
ObjectReader reader,
|
||||||
|
List<Comment> putInComments,
|
||||||
|
List<Comment> deletedComments)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
RevisionNoteMap<ChangeRevisionNote> revNotesMap =
|
||||||
|
RevisionNoteMap.parse(noteUtil, changeId, reader, parentNoteMap, PUBLISHED);
|
||||||
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
|
||||||
|
|
||||||
|
for (Comment c : putInComments) {
|
||||||
|
cache.get(new RevId(c.revId)).putComment(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Comment c : deletedComments) {
|
||||||
|
cache.get(new RevId(c.revId)).deleteComment(c.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
||||||
|
for (Map.Entry<RevId, RevisionNoteBuilder> entry : builders.entrySet()) {
|
||||||
|
ObjectId objectId = ObjectId.fromString(entry.getKey().get());
|
||||||
|
byte[] data = entry.getValue().build(noteUtil, noteUtil.getWriteJson());
|
||||||
|
if (data.length == 0) {
|
||||||
|
revNotesMap.noteMap.remove(objectId);
|
||||||
|
}
|
||||||
|
revNotesMap.noteMap.set(objectId, inserter.insert(OBJ_BLOB, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
CommitBuilder cb = new CommitBuilder();
|
||||||
|
cb.setParentId(parentId);
|
||||||
|
cb.setTreeId(revNotesMap.noteMap.writeTree(inserter));
|
||||||
|
cb.setMessage(originalCommit.getFullMessage());
|
||||||
|
cb.setCommitter(originalCommit.getCommitterIdent());
|
||||||
|
cb.setAuthor(originalCommit.getAuthorIdent());
|
||||||
|
cb.setEncoding(originalCommit.getEncoding());
|
||||||
|
|
||||||
|
return inserter.insert(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ public class NoteDbModule extends FactoryModule {
|
|||||||
public void configure() {
|
public void configure() {
|
||||||
factory(ChangeUpdate.Factory.class);
|
factory(ChangeUpdate.Factory.class);
|
||||||
factory(ChangeDraftUpdate.Factory.class);
|
factory(ChangeDraftUpdate.Factory.class);
|
||||||
|
factory(DeleteCommentRewriter.Factory.class);
|
||||||
factory(DraftCommentNotes.Factory.class);
|
factory(DraftCommentNotes.Factory.class);
|
||||||
factory(RobotCommentUpdate.Factory.class);
|
factory(RobotCommentUpdate.Factory.class);
|
||||||
factory(RobotCommentNotes.Factory.class);
|
factory(RobotCommentNotes.Factory.class);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (C) 2017 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
|
public interface NoteDbRewriter {
|
||||||
|
|
||||||
|
/** Gets the name of the target ref which will be rewritten. */
|
||||||
|
String getRefName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites the commit history.
|
||||||
|
*
|
||||||
|
* @param revWalk a {@code RevWalk} instance.
|
||||||
|
* @param inserter a {@code ObjectInserter} instance.
|
||||||
|
* @param currTip the {@code ObjectId} of the ref's tip commit.
|
||||||
|
* @return the {@code ObjectId} of the ref's new tip commit.
|
||||||
|
*/
|
||||||
|
ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip)
|
||||||
|
throws IOException, ConfigInvalidException, OrmException;
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||||
import org.eclipse.jgit.lib.NullProgressMonitor;
|
import org.eclipse.jgit.lib.NullProgressMonitor;
|
||||||
@@ -205,6 +206,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
private final ListMultimap<String, ChangeUpdate> changeUpdates;
|
private final ListMultimap<String, ChangeUpdate> changeUpdates;
|
||||||
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
|
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
|
||||||
private final ListMultimap<String, RobotCommentUpdate> robotCommentUpdates;
|
private final ListMultimap<String, RobotCommentUpdate> robotCommentUpdates;
|
||||||
|
private final ListMultimap<String, NoteDbRewriter> rewriters;
|
||||||
private final Set<Change.Id> toDelete;
|
private final Set<Change.Id> toDelete;
|
||||||
|
|
||||||
private OpenRepo changeRepo;
|
private OpenRepo changeRepo;
|
||||||
@@ -232,6 +234,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
changeUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
changeUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
robotCommentUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
robotCommentUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
|
rewriters = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
toDelete = new HashSet<>();
|
toDelete = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +347,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
return changeUpdates.isEmpty()
|
return changeUpdates.isEmpty()
|
||||||
&& draftUpdates.isEmpty()
|
&& draftUpdates.isEmpty()
|
||||||
&& robotCommentUpdates.isEmpty()
|
&& robotCommentUpdates.isEmpty()
|
||||||
|
&& rewriters.isEmpty()
|
||||||
&& toDelete.isEmpty()
|
&& toDelete.isEmpty()
|
||||||
&& !hasCommands(changeRepo)
|
&& !hasCommands(changeRepo)
|
||||||
&& !hasCommands(allUsersRepo);
|
&& !hasCommands(allUsersRepo);
|
||||||
@@ -377,6 +381,10 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
if (rcu != null) {
|
if (rcu != null) {
|
||||||
robotCommentUpdates.put(rcu.getRefName(), rcu);
|
robotCommentUpdates.put(rcu.getRefName(), rcu);
|
||||||
}
|
}
|
||||||
|
DeleteCommentRewriter deleteCommentRewriter = update.getDeleteCommentRewriter();
|
||||||
|
if (deleteCommentRewriter != null) {
|
||||||
|
rewriters.put(deleteCommentRewriter.getRefName(), deleteCommentRewriter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(ChangeDraftUpdate draftUpdate) {
|
public void add(ChangeDraftUpdate draftUpdate) {
|
||||||
@@ -603,6 +611,21 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
if (!robotCommentUpdates.isEmpty()) {
|
if (!robotCommentUpdates.isEmpty()) {
|
||||||
addUpdates(robotCommentUpdates, changeRepo);
|
addUpdates(robotCommentUpdates, changeRepo);
|
||||||
}
|
}
|
||||||
|
if (!rewriters.isEmpty()) {
|
||||||
|
Optional<String> conflictKey =
|
||||||
|
rewriters
|
||||||
|
.keySet()
|
||||||
|
.stream()
|
||||||
|
.filter(k -> (draftUpdates.containsKey(k) || robotCommentUpdates.containsKey(k)))
|
||||||
|
.findAny();
|
||||||
|
if (conflictKey.isPresent()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format(
|
||||||
|
"cannot update and rewrite ref %s in one BatchUpdate", conflictKey.get()));
|
||||||
|
}
|
||||||
|
addRewrites(rewriters, changeRepo);
|
||||||
|
}
|
||||||
|
|
||||||
for (Change.Id id : toDelete) {
|
for (Change.Id id : toDelete) {
|
||||||
doDelete(id);
|
doDelete(id);
|
||||||
}
|
}
|
||||||
@@ -723,6 +746,35 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void addRewrites(ListMultimap<String, NoteDbRewriter> rewriters, OpenRepo openRepo)
|
||||||
|
throws OrmException, IOException {
|
||||||
|
for (Map.Entry<String, Collection<NoteDbRewriter>> entry : rewriters.asMap().entrySet()) {
|
||||||
|
String refName = entry.getKey();
|
||||||
|
ObjectId oldTip = openRepo.cmds.get(refName).orElse(ObjectId.zeroId());
|
||||||
|
|
||||||
|
if (oldTip.equals(ObjectId.zeroId())) {
|
||||||
|
throw new OrmException(String.format("Ref %s is empty", refName));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectId currTip = oldTip;
|
||||||
|
try {
|
||||||
|
for (NoteDbRewriter noteDbRewriter : entry.getValue()) {
|
||||||
|
ObjectId nextTip =
|
||||||
|
noteDbRewriter.rewriteCommitHistory(openRepo.rw, openRepo.tempIns, currTip);
|
||||||
|
if (nextTip != null) {
|
||||||
|
currTip = nextTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new OrmException("Cannot rewrite commit history", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldTip.equals(currTip)) {
|
||||||
|
openRepo.cmds.add(new ReceiveCommand(oldTip, currTip, refName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static <U extends AbstractChangeUpdate> boolean allowWrite(
|
private static <U extends AbstractChangeUpdate> boolean allowWrite(
|
||||||
Collection<U> updates, ObjectId old) {
|
Collection<U> updates, ObjectId old) {
|
||||||
if (!old.equals(ObjectId.zeroId())) {
|
if (!old.equals(ObjectId.zeroId())) {
|
||||||
|
|||||||
Reference in New Issue
Block a user