Add support for Robot Comments
Extend the PostReview REST endpoint to support posting of robot comments. For this ReviewInput got a new field that can contain a list of robot comments. For reading robot comments new REST endpoints have been added. Robot comments are only supported with NoteDb, but not with ReviewDb. Robot comments are always stored as JSON, even if notedb.writeJson is set to false. In NoteDb robot comments are not stored in the change meta ref but in a separate refs/changes/XX/YYYY/robot-comments ref. By storing robot comments in a separate ref we can delete robot comments without rewriting the history of the change meta branch which is needed for auditing and hence cannot be rewritten. Deletion of robot comments is not implemented in this change, but we may want to support this later as the amount and size of robot comments can be large. Draft robot comments are not supported, but robot comments are always published comments. Change-Id: I2d8a5ca59e9a8b2c34863c54a3a9576599f69526 Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
49
Documentation/config-robot-comments.txt
Normal file
49
Documentation/config-robot-comments.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
= Gerrit Code Review - Robot Comments
|
||||||
|
|
||||||
|
Gerrit has special support for inline comments that are generated by
|
||||||
|
automated third-party systems, so called "robot comments". For example
|
||||||
|
robot comments can be used to represent the results of code analyzers.
|
||||||
|
|
||||||
|
In contrast to regular inline comments which are free-text comments,
|
||||||
|
robot comments are more structured and can contain additional data,
|
||||||
|
such as a robot ID, a robot run ID and a URL, see
|
||||||
|
link:rest-api-changes.html#robot-comment-info[RobotCommentInfo] for
|
||||||
|
details.
|
||||||
|
|
||||||
|
It is planned to visualize robot comments differently in the web UI so
|
||||||
|
that they can be easily distinguished from human comments. Users should
|
||||||
|
also be able to use filtering on robot comments, so that only part of
|
||||||
|
the robot comments or no robot comments are shown. In addition it is
|
||||||
|
planned that robot comments can contain fixes, that users can apply by
|
||||||
|
a single click.
|
||||||
|
|
||||||
|
== REST endpoints
|
||||||
|
|
||||||
|
* Posting robot comments is done by the
|
||||||
|
link:rest-api-changes.html[Set Review] REST endpoint. The
|
||||||
|
link:rest-api-changes.html#review-input[input] for this REST endpoint
|
||||||
|
can contain robot comments in its `robot_comments` field.
|
||||||
|
* link:rest-api-changes.html#list-robot-comments[List Robot Comments]
|
||||||
|
* link:rest-api-changes.html#get-robot-comment[Get Robot Comment]
|
||||||
|
|
||||||
|
== Storage
|
||||||
|
|
||||||
|
Robot comments are stored per change in a
|
||||||
|
`refs/changes/XX/YYYY/robot-comments` ref, where `XX/YYYY` is the
|
||||||
|
sharded change ID.
|
||||||
|
|
||||||
|
Robot comments can be dropped by deleting this ref.
|
||||||
|
|
||||||
|
== Limitations
|
||||||
|
|
||||||
|
* Robot comments are only supported with NoteDb, but not with ReviewDb.
|
||||||
|
* Robot comments are not displayed in the web UI yet.
|
||||||
|
* There is no support for draft robot comments, but robot comments are
|
||||||
|
always published and visible to everyone who can see the change.
|
||||||
|
|
||||||
|
GERRIT
|
||||||
|
------
|
||||||
|
Part of link:index.html[Gerrit Code Review]
|
||||||
|
|
||||||
|
SEARCHBOX
|
||||||
|
---------
|
@@ -45,6 +45,7 @@
|
|||||||
. link:config-hooks.html[Hooks]
|
. link:config-hooks.html[Hooks]
|
||||||
. link:config-mail.html[Mail Templates]
|
. link:config-mail.html[Mail Templates]
|
||||||
. link:config-cla.html[Contributor Agreements]
|
. link:config-cla.html[Contributor Agreements]
|
||||||
|
. link:config-robot-comments.html[Robot Comments]
|
||||||
|
|
||||||
== Server Administration
|
== Server Administration
|
||||||
. link:install.html[Installation Guide]
|
. link:install.html[Installation Guide]
|
||||||
|
@@ -3904,6 +3904,102 @@ describes the published comment.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[list-comments]]
|
||||||
|
=== List Robot Comments
|
||||||
|
--
|
||||||
|
'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/robotcomments/'
|
||||||
|
--
|
||||||
|
|
||||||
|
Lists the link:config-robot-comments.html[robot comments] of a
|
||||||
|
revision.
|
||||||
|
|
||||||
|
As result a map is returned that maps the file path to a list of
|
||||||
|
link:#robot-comment-info[RobotCommentInfo] entries. The entries in the
|
||||||
|
map are sorted by file path.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/robotcomments/ HTTP/1.0
|
||||||
|
----
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
)]}'
|
||||||
|
{
|
||||||
|
"gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java": [
|
||||||
|
{
|
||||||
|
"id": "TvcXrmjM",
|
||||||
|
"line": 23,
|
||||||
|
"message": "unused import",
|
||||||
|
"updated": "2016-02-26 15:40:43.986000000",
|
||||||
|
"author": {
|
||||||
|
"_account_id": 1000110,
|
||||||
|
"name": "Code Analyzer",
|
||||||
|
"email": "code.analyzer@example.com"
|
||||||
|
},
|
||||||
|
"robotId": "importChecker",
|
||||||
|
"robotRunId": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TveXwFiA",
|
||||||
|
"line": 49,
|
||||||
|
"message": "wrong indention",
|
||||||
|
"updated": "2016-02-26 15:40:45.328000000",
|
||||||
|
"author": {
|
||||||
|
"_account_id": 1000110,
|
||||||
|
"name": "Code Analyzer",
|
||||||
|
"email": "code.analyzer@example.com"
|
||||||
|
},
|
||||||
|
"robotId": "styleChecker",
|
||||||
|
"robotRunId": "5c606c425dd45184484f9d0a2ffd725a7607839b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
[[get-robot-comment]]
|
||||||
|
=== Get Robot Comment
|
||||||
|
--
|
||||||
|
'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/robotcomments/link:#comment-id[\{comment-id\}]'
|
||||||
|
--
|
||||||
|
|
||||||
|
Retrieves a link:config-robot-comments.html[robot comment] of a
|
||||||
|
revision.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/robotcomments/TvcXrmjM HTTP/1.0
|
||||||
|
----
|
||||||
|
|
||||||
|
As response a link:#robot-comment-info[RobotCommentInfo] entity is
|
||||||
|
returned that describes the robot comment.
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
)]}'
|
||||||
|
{
|
||||||
|
"id": "TvcXrmjM",
|
||||||
|
"line": 23,
|
||||||
|
"message": "unused import",
|
||||||
|
"updated": "2016-02-26 15:40:43.986000000",
|
||||||
|
"author": {
|
||||||
|
"_account_id": 1000110,
|
||||||
|
"name": "Code Analyzer",
|
||||||
|
"email": "code.analyzer@example.com"
|
||||||
|
},
|
||||||
|
"robotId": "importChecker",
|
||||||
|
"robotRunId": "76b1375aa8626ea7149792831fe2ed85e80d9e04"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
[[list-files]]
|
[[list-files]]
|
||||||
=== List Files
|
=== List Files
|
||||||
--
|
--
|
||||||
@@ -5465,6 +5561,9 @@ label names to the voting values.
|
|||||||
|`comments` |optional|
|
|`comments` |optional|
|
||||||
The comments that should be added as a map that maps a file path to a
|
The comments that should be added as a map that maps a file path to a
|
||||||
list of link:#comment-input[CommentInput] entities.
|
list of link:#comment-input[CommentInput] entities.
|
||||||
|
|`robot_comments` |optional|
|
||||||
|
The robot comments that should be added as a map that maps a file path
|
||||||
|
to a list of link:#robot-comment-input[RobotCommentInput] entities.
|
||||||
|`strict_labels` |`true` if not set|
|
|`strict_labels` |`true` if not set|
|
||||||
Whether all labels are required to be within the user's permitted ranges
|
Whether all labels are required to be within the user's permitted ranges
|
||||||
based on access controls. +
|
based on access controls. +
|
||||||
@@ -5591,6 +5690,29 @@ This field is always set if the option is requested; if no push
|
|||||||
certificate was provided, it is set to an empty object.
|
certificate was provided, it is set to an empty object.
|
||||||
|===========================
|
|===========================
|
||||||
|
|
||||||
|
[[robot-comment-info]]
|
||||||
|
=== RobotCommentInfo
|
||||||
|
The `RobotCommentInfo` entity contains information about a robot inline
|
||||||
|
comment.
|
||||||
|
|
||||||
|
`RobotCommentInfo` has the same fields as link:#[CommentInfo].
|
||||||
|
In addition `RobotCommentInfo` has the following fields:
|
||||||
|
|
||||||
|
[options="header",cols="1,^1,5"]
|
||||||
|
|===========================
|
||||||
|
|Field Name ||Description
|
||||||
|
|`robot_id` ||The ID of the robot that generated this comment.
|
||||||
|
|`robot_run_id` ||An ID of the run of the robot.
|
||||||
|
|`url` |optional|URL to more information.
|
||||||
|
|===========================
|
||||||
|
|
||||||
|
[[robot-comment-input]]
|
||||||
|
=== RobotCommentInput
|
||||||
|
The `RobotCommentInput` entity contains information for creating an inline
|
||||||
|
robot comment.
|
||||||
|
|
||||||
|
`RobotCommentInput` has the same fields as link:#[RobotCommentInfo].
|
||||||
|
|
||||||
[[rule-input]]
|
[[rule-input]]
|
||||||
=== RuleInput
|
=== RuleInput
|
||||||
The `RuleInput` entity contains information to test a Prolog rule.
|
The `RuleInput` entity contains information to test a Prolog rule.
|
||||||
|
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (C) 2016 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.acceptance.api.revision;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
|
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class RobotCommentsIT extends AbstractDaemonTest {
|
||||||
|
@Test
|
||||||
|
public void comments() throws Exception {
|
||||||
|
assume().that(notesMigration.enabled()).isTrue();
|
||||||
|
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
RobotCommentInput in = createRobotCommentInput();
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
Map<String, List<RobotCommentInput>> robotComments = new HashMap<>();
|
||||||
|
robotComments.put(in.path, Collections.singletonList(in));
|
||||||
|
reviewInput.robotComments = robotComments;
|
||||||
|
reviewInput.message = "comment test";
|
||||||
|
gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.current()
|
||||||
|
.review(reviewInput);
|
||||||
|
|
||||||
|
Map<String, List<RobotCommentInfo>> out = gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.revision(r.getCommit().name())
|
||||||
|
.robotComments();
|
||||||
|
assertThat(out).hasSize(1);
|
||||||
|
RobotCommentInfo comment = Iterables.getOnlyElement(out.get(in.path));
|
||||||
|
assertRobotComment(comment, in, false);
|
||||||
|
|
||||||
|
List<RobotCommentInfo> list = gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.revision(r.getCommit().name())
|
||||||
|
.robotCommentsAsList();
|
||||||
|
assertThat(list).hasSize(1);
|
||||||
|
|
||||||
|
RobotCommentInfo comment2 = list.get(0);
|
||||||
|
assertRobotComment(comment2, in);
|
||||||
|
|
||||||
|
RobotCommentInfo comment3 = gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.revision(r.getCommit().name())
|
||||||
|
.robotComment(comment.id)
|
||||||
|
.get();
|
||||||
|
assertRobotComment(comment3, in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void robotCommentsNotSupported() throws Exception {
|
||||||
|
assume().that(notesMigration.enabled()).isFalse();
|
||||||
|
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
RobotCommentInput in = createRobotCommentInput();
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
Map<String, List<RobotCommentInput>> robotComments = new HashMap<>();
|
||||||
|
robotComments.put(FILE_NAME, Collections.singletonList(in));
|
||||||
|
reviewInput.robotComments = robotComments;
|
||||||
|
reviewInput.message = "comment test";
|
||||||
|
|
||||||
|
exception.expect(MethodNotAllowedException.class);
|
||||||
|
exception.expectMessage("robot comments not supported");
|
||||||
|
gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.current()
|
||||||
|
.review(reviewInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RobotCommentInput createRobotCommentInput() {
|
||||||
|
RobotCommentInput in = new RobotCommentInput();
|
||||||
|
in.robotId = "happyRobot";
|
||||||
|
in.robotRunId = "1";
|
||||||
|
in.url = "http://www.happy-robot.com";
|
||||||
|
in.line = 1;
|
||||||
|
in.message = "nit: trailing whitespace";
|
||||||
|
in.path = FILE_NAME;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertRobotComment(RobotCommentInfo c,
|
||||||
|
RobotCommentInput expected) {
|
||||||
|
assertRobotComment(c, expected, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertRobotComment(RobotCommentInfo c,
|
||||||
|
RobotCommentInput expected, boolean expectPath) {
|
||||||
|
assertThat(c.robotId).isEqualTo(expected.robotId);
|
||||||
|
assertThat(c.robotRunId).isEqualTo(expected.robotRunId);
|
||||||
|
assertThat(c.url).isEqualTo(expected.url);
|
||||||
|
assertThat(c.line).isEqualTo(expected.line);
|
||||||
|
assertThat(c.message).isEqualTo(expected.message);
|
||||||
|
|
||||||
|
assertThat(c.author.email).isEqualTo(admin.email);
|
||||||
|
|
||||||
|
if (expectPath) {
|
||||||
|
assertThat(c.path).isEqualTo(expected.path);
|
||||||
|
} else {
|
||||||
|
assertThat(c.path).isNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -34,6 +34,7 @@ public class ReviewInput {
|
|||||||
|
|
||||||
public Map<String, Short> labels;
|
public Map<String, Short> labels;
|
||||||
public Map<String, List<CommentInput>> comments;
|
public Map<String, List<CommentInput>> comments;
|
||||||
|
public Map<String, List<RobotCommentInput>> robotComments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true require all labels to be within the user's permitted ranges based
|
* If true require all labels to be within the user's permitted ranges based
|
||||||
@@ -94,6 +95,12 @@ public class ReviewInput {
|
|||||||
public static class CommentInput extends Comment {
|
public static class CommentInput extends Comment {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class RobotCommentInput extends CommentInput {
|
||||||
|
public String robotId;
|
||||||
|
public String robotRunId;
|
||||||
|
public String url;
|
||||||
|
}
|
||||||
|
|
||||||
public ReviewInput message(String msg) {
|
public ReviewInput message(String msg) {
|
||||||
message = msg != null && !msg.isEmpty() ? msg : null;
|
message = msg != null && !msg.isEmpty() ? msg : null;
|
||||||
return this;
|
return this;
|
||||||
|
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.common.CommentInfo;
|
|||||||
import com.google.gerrit.extensions.common.CommitInfo;
|
import com.google.gerrit.extensions.common.CommitInfo;
|
||||||
import com.google.gerrit.extensions.common.FileInfo;
|
import com.google.gerrit.extensions.common.FileInfo;
|
||||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
|
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
|
||||||
import com.google.gerrit.extensions.restapi.BinaryResult;
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
import com.google.gerrit.extensions.restapi.NotImplementedException;
|
import com.google.gerrit.extensions.restapi.NotImplementedException;
|
||||||
@@ -54,15 +55,18 @@ public interface RevisionApi {
|
|||||||
MergeableInfo mergeableOtherBranches() throws RestApiException;
|
MergeableInfo mergeableOtherBranches() throws RestApiException;
|
||||||
|
|
||||||
Map<String, List<CommentInfo>> comments() throws RestApiException;
|
Map<String, List<CommentInfo>> comments() throws RestApiException;
|
||||||
|
Map<String, List<RobotCommentInfo>> robotComments() throws RestApiException;
|
||||||
Map<String, List<CommentInfo>> drafts() throws RestApiException;
|
Map<String, List<CommentInfo>> drafts() throws RestApiException;
|
||||||
|
|
||||||
List<CommentInfo> commentsAsList() throws RestApiException;
|
List<CommentInfo> commentsAsList() throws RestApiException;
|
||||||
List<CommentInfo> draftsAsList() throws RestApiException;
|
List<CommentInfo> draftsAsList() throws RestApiException;
|
||||||
|
List<RobotCommentInfo> robotCommentsAsList() throws RestApiException;
|
||||||
|
|
||||||
DraftApi createDraft(DraftInput in) throws RestApiException;
|
DraftApi createDraft(DraftInput in) throws RestApiException;
|
||||||
DraftApi draft(String id) throws RestApiException;
|
DraftApi draft(String id) throws RestApiException;
|
||||||
|
|
||||||
CommentApi comment(String id) throws RestApiException;
|
CommentApi comment(String id) throws RestApiException;
|
||||||
|
RobotCommentApi robotComment(String id) throws RestApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns patch of revision.
|
* Returns patch of revision.
|
||||||
@@ -196,6 +200,12 @@ public interface RevisionApi {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<RobotCommentInfo>> robotComments()
|
||||||
|
throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CommentInfo> commentsAsList() throws RestApiException {
|
public List<CommentInfo> commentsAsList() throws RestApiException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -206,6 +216,12 @@ public interface RevisionApi {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RobotCommentInfo> robotCommentsAsList()
|
||||||
|
throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, List<CommentInfo>> drafts() throws RestApiException {
|
public Map<String, List<CommentInfo>> drafts() throws RestApiException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -226,6 +242,11 @@ public interface RevisionApi {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RobotCommentApi robotComment(String id) throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinaryResult patch() throws RestApiException {
|
public BinaryResult patch() throws RestApiException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (C) 2016 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.common.RobotCommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.NotImplementedException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
|
||||||
|
public interface RobotCommentApi {
|
||||||
|
RobotCommentInfo get() throws RestApiException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default implementation which allows source compatibility
|
||||||
|
* when adding new methods to the interface.
|
||||||
|
**/
|
||||||
|
class NotImplemented implements RobotCommentApi {
|
||||||
|
@Override
|
||||||
|
public RobotCommentInfo get() throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (C) 2016 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.extensions.common;
|
||||||
|
|
||||||
|
public class RobotCommentInfo extends CommentInfo {
|
||||||
|
public String robotId;
|
||||||
|
public String robotRunId;
|
||||||
|
public String url;
|
||||||
|
}
|
@@ -149,6 +149,7 @@ public final class Change {
|
|||||||
}
|
}
|
||||||
int ce = nextNonDigit(ref, cs);
|
int ce = nextNonDigit(ref, cs);
|
||||||
if (ref.substring(ce).equals(RefNames.META_SUFFIX)
|
if (ref.substring(ce).equals(RefNames.META_SUFFIX)
|
||||||
|
|| ref.substring(ce).equals(RefNames.ROBOT_COMMENTS_SUFFIX)
|
||||||
|| PatchSet.Id.fromRef(ref, ce) >= 0) {
|
|| PatchSet.Id.fromRef(ref, ce) >= 0) {
|
||||||
return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
|
return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
|
||||||
}
|
}
|
||||||
|
@@ -68,6 +68,9 @@ public class RefNames {
|
|||||||
/** Suffix of a meta ref in the NoteDb. */
|
/** Suffix of a meta ref in the NoteDb. */
|
||||||
public static final String META_SUFFIX = "/meta";
|
public static final String META_SUFFIX = "/meta";
|
||||||
|
|
||||||
|
/** Suffix of a ref that stores robot comments in the NoteDb. */
|
||||||
|
public static final String ROBOT_COMMENTS_SUFFIX = "/robot-comments";
|
||||||
|
|
||||||
public static final String EDIT_PREFIX = "edit-";
|
public static final String EDIT_PREFIX = "edit-";
|
||||||
|
|
||||||
public static String fullName(String ref) {
|
public static String fullName(String ref) {
|
||||||
@@ -92,6 +95,14 @@ public class RefNames {
|
|||||||
return r.toString();
|
return r.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String robotCommentsRef(Change.Id id) {
|
||||||
|
StringBuilder r = new StringBuilder();
|
||||||
|
r.append(REFS_CHANGES);
|
||||||
|
r.append(shard(id.get()));
|
||||||
|
r.append(ROBOT_COMMENTS_SUFFIX);
|
||||||
|
return r.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String refsUsers(Account.Id accountId) {
|
public static String refsUsers(Account.Id accountId) {
|
||||||
StringBuilder r = new StringBuilder();
|
StringBuilder r = new StringBuilder();
|
||||||
r.append(REFS_USERS);
|
r.append(REFS_USERS);
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (C) 2016 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.reviewdb.client;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class RobotComment extends Comment {
|
||||||
|
public String robotId;
|
||||||
|
public String robotRunId;
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
public RobotComment(Key key, Account.Id author, Timestamp writtenOn,
|
||||||
|
short side, String message, String serverId, String robotId,
|
||||||
|
String robotRunId) {
|
||||||
|
super(key, author, writtenOn, side, message, serverId);
|
||||||
|
this.robotId = robotId;
|
||||||
|
this.robotRunId = robotRunId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder()
|
||||||
|
.append("RobotComment{")
|
||||||
|
.append("key=").append(key).append(',')
|
||||||
|
.append("robotId=").append(robotId).append(',')
|
||||||
|
.append("robotRunId=").append(robotRunId).append(',')
|
||||||
|
.append("lineNbr=").append(lineNbr).append(',')
|
||||||
|
.append("author=").append(author.getId().get()).append(',')
|
||||||
|
.append("writtenOn=").append(writtenOn.toString()).append(',')
|
||||||
|
.append("side=").append(side).append(',')
|
||||||
|
.append("message=").append(Objects.toString(message, "")).append(',')
|
||||||
|
.append("parentUuid=")
|
||||||
|
.append(Objects.toString(parentUuid, "")).append(',')
|
||||||
|
.append("range=").append(Objects.toString(range, "")).append(',')
|
||||||
|
.append("revId=").append(revId != null ? revId : "").append(',')
|
||||||
|
.append("tag=").append(Objects.toString(tag, "")).append(',')
|
||||||
|
.append("url=").append(url)
|
||||||
|
.append('}')
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ import static java.util.stream.Collectors.toList;
|
|||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ComparisonChain;
|
import com.google.common.collect.ComparisonChain;
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
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.common.collect.Ordering;
|
import com.google.common.collect.Ordering;
|
||||||
@@ -34,6 +35,7 @@ import com.google.gerrit.reviewdb.client.PatchLineComment;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.config.GerritServerId;
|
import com.google.gerrit.server.config.GerritServerId;
|
||||||
@@ -156,9 +158,17 @@ public class CommentsUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notes.load();
|
notes.load();
|
||||||
List<Comment> comments = new ArrayList<>();
|
return sort(Lists.newArrayList(notes.getComments().values()));
|
||||||
comments.addAll(notes.getComments().values());
|
}
|
||||||
return sort(comments);
|
|
||||||
|
public List<RobotComment> robotCommentsByChange(ChangeNotes notes)
|
||||||
|
throws OrmException {
|
||||||
|
if (!migration.readChanges()) {
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
notes.load();
|
||||||
|
return sort(Lists.newArrayList(notes.getRobotComments().values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Comment> draftByChange(ReviewDb db, ChangeNotes notes)
|
public List<Comment> draftByChange(ReviewDb db, ChangeNotes notes)
|
||||||
@@ -221,6 +231,14 @@ public class CommentsUtil {
|
|||||||
commentsOnPatchSet(notes.load().getComments().values(), psId));
|
commentsOnPatchSet(notes.load().getComments().values(), psId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<RobotComment> robotCommentsByPatchSet(ChangeNotes notes,
|
||||||
|
PatchSet.Id psId) throws OrmException {
|
||||||
|
if (!migration.readChanges()) {
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
return commentsOnPatchSet(notes.load().getRobotComments().values(), psId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the commit message the A side in a diff view is always empty when a
|
* For the commit message the A side in a diff view is always empty when a
|
||||||
* comparison against an ancestor is done, so there can't be any comments on
|
* comparison against an ancestor is done, so there can't be any comments on
|
||||||
@@ -309,6 +327,13 @@ public class CommentsUtil {
|
|||||||
.upsert(toPatchLineComments(update.getId(), status, comments));
|
.upsert(toPatchLineComments(update.getId(), status, comments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putRobotComments(ChangeUpdate update,
|
||||||
|
Iterable<RobotComment> comments) {
|
||||||
|
for (RobotComment c : comments) {
|
||||||
|
update.putRobotComment(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteComments(ReviewDb db, ChangeUpdate update,
|
public void deleteComments(ReviewDb db, ChangeUpdate update,
|
||||||
Iterable<Comment> comments) throws OrmException {
|
Iterable<Comment> comments) throws OrmException {
|
||||||
for (Comment c : comments) {
|
for (Comment c : comments) {
|
||||||
@@ -352,11 +377,11 @@ public class CommentsUtil {
|
|||||||
return sort(result);
|
return sort(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Comment> commentsOnPatchSet(
|
private static <T extends Comment> List<T> commentsOnPatchSet(
|
||||||
Collection<Comment> allComments,
|
Collection<T> allComments,
|
||||||
PatchSet.Id psId) {
|
PatchSet.Id psId) {
|
||||||
List<Comment> result = new ArrayList<>(allComments.size());
|
List<T> result = new ArrayList<>(allComments.size());
|
||||||
for (Comment c : allComments) {
|
for (T c : allComments) {
|
||||||
if (c.key.patchSetId == psId.get()) {
|
if (c.key.patchSetId == psId.get()) {
|
||||||
result.add(c);
|
result.add(c);
|
||||||
}
|
}
|
||||||
@@ -400,7 +425,7 @@ public class CommentsUtil {
|
|||||||
RefNames.refsDraftCommentsPrefix(changeId)).values();
|
RefNames.refsDraftCommentsPrefix(changeId)).values();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Comment> sort(List<Comment> comments) {
|
private static <T extends Comment> List<T> sort(List<T> comments) {
|
||||||
Collections.sort(comments, COMMENT_ORDER);
|
Collections.sort(comments, COMMENT_ORDER);
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ public class Module extends FactoryModule {
|
|||||||
|
|
||||||
factory(ChangeApiImpl.Factory.class);
|
factory(ChangeApiImpl.Factory.class);
|
||||||
factory(CommentApiImpl.Factory.class);
|
factory(CommentApiImpl.Factory.class);
|
||||||
|
factory(RobotCommentApiImpl.Factory.class);
|
||||||
factory(DraftApiImpl.Factory.class);
|
factory(DraftApiImpl.Factory.class);
|
||||||
factory(RevisionApiImpl.Factory.class);
|
factory(RevisionApiImpl.Factory.class);
|
||||||
factory(FileApiImpl.Factory.class);
|
factory(FileApiImpl.Factory.class);
|
||||||
|
@@ -26,6 +26,7 @@ import com.google.gerrit.extensions.api.changes.FileApi;
|
|||||||
import com.google.gerrit.extensions.api.changes.RebaseInput;
|
import com.google.gerrit.extensions.api.changes.RebaseInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
import com.google.gerrit.extensions.api.changes.RevisionApi;
|
import com.google.gerrit.extensions.api.changes.RevisionApi;
|
||||||
|
import com.google.gerrit.extensions.api.changes.RobotCommentApi;
|
||||||
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
import com.google.gerrit.extensions.common.ActionInfo;
|
import com.google.gerrit.extensions.common.ActionInfo;
|
||||||
@@ -33,6 +34,7 @@ import com.google.gerrit.extensions.common.CommentInfo;
|
|||||||
import com.google.gerrit.extensions.common.CommitInfo;
|
import com.google.gerrit.extensions.common.CommitInfo;
|
||||||
import com.google.gerrit.extensions.common.FileInfo;
|
import com.google.gerrit.extensions.common.FileInfo;
|
||||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
|
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
|
||||||
import com.google.gerrit.extensions.restapi.BinaryResult;
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
import com.google.gerrit.extensions.restapi.IdString;
|
import com.google.gerrit.extensions.restapi.IdString;
|
||||||
@@ -50,6 +52,7 @@ import com.google.gerrit.server.change.GetPatch;
|
|||||||
import com.google.gerrit.server.change.GetRevisionActions;
|
import com.google.gerrit.server.change.GetRevisionActions;
|
||||||
import com.google.gerrit.server.change.ListRevisionComments;
|
import com.google.gerrit.server.change.ListRevisionComments;
|
||||||
import com.google.gerrit.server.change.ListRevisionDrafts;
|
import com.google.gerrit.server.change.ListRevisionDrafts;
|
||||||
|
import com.google.gerrit.server.change.ListRobotComments;
|
||||||
import com.google.gerrit.server.change.Mergeable;
|
import com.google.gerrit.server.change.Mergeable;
|
||||||
import com.google.gerrit.server.change.PostReview;
|
import com.google.gerrit.server.change.PostReview;
|
||||||
import com.google.gerrit.server.change.PreviewSubmit;
|
import com.google.gerrit.server.change.PreviewSubmit;
|
||||||
@@ -58,6 +61,7 @@ import com.google.gerrit.server.change.Rebase;
|
|||||||
import com.google.gerrit.server.change.RebaseUtil;
|
import com.google.gerrit.server.change.RebaseUtil;
|
||||||
import com.google.gerrit.server.change.Reviewed;
|
import com.google.gerrit.server.change.Reviewed;
|
||||||
import com.google.gerrit.server.change.RevisionResource;
|
import com.google.gerrit.server.change.RevisionResource;
|
||||||
|
import com.google.gerrit.server.change.RobotComments;
|
||||||
import com.google.gerrit.server.change.Submit;
|
import com.google.gerrit.server.change.Submit;
|
||||||
import com.google.gerrit.server.change.TestSubmitType;
|
import com.google.gerrit.server.change.TestSubmitType;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@@ -101,12 +105,15 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
private final Mergeable mergeable;
|
private final Mergeable mergeable;
|
||||||
private final FileApiImpl.Factory fileApi;
|
private final FileApiImpl.Factory fileApi;
|
||||||
private final ListRevisionComments listComments;
|
private final ListRevisionComments listComments;
|
||||||
|
private final ListRobotComments listRobotComments;
|
||||||
private final ListRevisionDrafts listDrafts;
|
private final ListRevisionDrafts listDrafts;
|
||||||
private final CreateDraftComment createDraft;
|
private final CreateDraftComment createDraft;
|
||||||
private final DraftComments drafts;
|
private final DraftComments drafts;
|
||||||
private final DraftApiImpl.Factory draftFactory;
|
private final DraftApiImpl.Factory draftFactory;
|
||||||
private final Comments comments;
|
private final Comments comments;
|
||||||
private final CommentApiImpl.Factory commentFactory;
|
private final CommentApiImpl.Factory commentFactory;
|
||||||
|
private final RobotComments robotComments;
|
||||||
|
private final RobotCommentApiImpl.Factory robotCommentFactory;
|
||||||
private final GetRevisionActions revisionActions;
|
private final GetRevisionActions revisionActions;
|
||||||
private final TestSubmitType testSubmitType;
|
private final TestSubmitType testSubmitType;
|
||||||
private final TestSubmitType.Get getSubmitType;
|
private final TestSubmitType.Get getSubmitType;
|
||||||
@@ -131,12 +138,15 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
Mergeable mergeable,
|
Mergeable mergeable,
|
||||||
FileApiImpl.Factory fileApi,
|
FileApiImpl.Factory fileApi,
|
||||||
ListRevisionComments listComments,
|
ListRevisionComments listComments,
|
||||||
|
ListRobotComments listRobotComments,
|
||||||
ListRevisionDrafts listDrafts,
|
ListRevisionDrafts listDrafts,
|
||||||
CreateDraftComment createDraft,
|
CreateDraftComment createDraft,
|
||||||
DraftComments drafts,
|
DraftComments drafts,
|
||||||
DraftApiImpl.Factory draftFactory,
|
DraftApiImpl.Factory draftFactory,
|
||||||
Comments comments,
|
Comments comments,
|
||||||
CommentApiImpl.Factory commentFactory,
|
CommentApiImpl.Factory commentFactory,
|
||||||
|
RobotComments robotComments,
|
||||||
|
RobotCommentApiImpl.Factory robotCommentFactory,
|
||||||
GetRevisionActions revisionActions,
|
GetRevisionActions revisionActions,
|
||||||
TestSubmitType testSubmitType,
|
TestSubmitType testSubmitType,
|
||||||
TestSubmitType.Get getSubmitType,
|
TestSubmitType.Get getSubmitType,
|
||||||
@@ -160,12 +170,15 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
this.mergeable = mergeable;
|
this.mergeable = mergeable;
|
||||||
this.fileApi = fileApi;
|
this.fileApi = fileApi;
|
||||||
this.listComments = listComments;
|
this.listComments = listComments;
|
||||||
|
this.robotComments = robotComments;
|
||||||
|
this.listRobotComments = listRobotComments;
|
||||||
this.listDrafts = listDrafts;
|
this.listDrafts = listDrafts;
|
||||||
this.createDraft = createDraft;
|
this.createDraft = createDraft;
|
||||||
this.drafts = drafts;
|
this.drafts = drafts;
|
||||||
this.draftFactory = draftFactory;
|
this.draftFactory = draftFactory;
|
||||||
this.comments = comments;
|
this.comments = comments;
|
||||||
this.commentFactory = commentFactory;
|
this.commentFactory = commentFactory;
|
||||||
|
this.robotCommentFactory = robotCommentFactory;
|
||||||
this.revisionActions = revisionActions;
|
this.revisionActions = revisionActions;
|
||||||
this.testSubmitType = testSubmitType;
|
this.testSubmitType = testSubmitType;
|
||||||
this.getSubmitType = getSubmitType;
|
this.getSubmitType = getSubmitType;
|
||||||
@@ -352,6 +365,15 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<RobotCommentInfo>> robotComments() throws RestApiException {
|
||||||
|
try {
|
||||||
|
return listRobotComments.apply(revision);
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new RestApiException("Cannot retrieve robot comments", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CommentInfo> commentsAsList() throws RestApiException {
|
public List<CommentInfo> commentsAsList() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
@@ -370,6 +392,15 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RobotCommentInfo> robotCommentsAsList() throws RestApiException {
|
||||||
|
try {
|
||||||
|
return listRobotComments.getComments(revision);
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new RestApiException("Cannot retrieve robot comments", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CommentInfo> draftsAsList() throws RestApiException {
|
public List<CommentInfo> draftsAsList() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
@@ -412,6 +443,16 @@ class RevisionApiImpl implements RevisionApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RobotCommentApi robotComment(String id) throws RestApiException {
|
||||||
|
try {
|
||||||
|
return robotCommentFactory
|
||||||
|
.create(robotComments.parse(revision, IdString.fromDecoded(id)));
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new RestApiException("Cannot retrieve robot comment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinaryResult patch() throws RestApiException {
|
public BinaryResult patch() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2016 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.api.changes;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.api.changes.RobotCommentApi;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.server.change.GetRobotComment;
|
||||||
|
import com.google.gerrit.server.change.RobotCommentResource;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
|
public class RobotCommentApiImpl implements RobotCommentApi {
|
||||||
|
interface Factory {
|
||||||
|
RobotCommentApiImpl create(RobotCommentResource c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final GetRobotComment getComment;
|
||||||
|
private final RobotCommentResource comment;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RobotCommentApiImpl(GetRobotComment getComment,
|
||||||
|
@Assisted RobotCommentResource comment) {
|
||||||
|
this.getComment = getComment;
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RobotCommentInfo get() throws RestApiException {
|
||||||
|
try {
|
||||||
|
return getComment.apply(comment);
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new RestApiException("Cannot retrieve robot comment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,8 +21,10 @@ import com.google.common.collect.FluentIterable;
|
|||||||
import com.google.gerrit.extensions.client.Comment.Range;
|
import com.google.gerrit.extensions.client.Comment.Range;
|
||||||
import com.google.gerrit.extensions.client.Side;
|
import com.google.gerrit.extensions.client.Side;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
import com.google.gerrit.extensions.restapi.Url;
|
import com.google.gerrit.extensions.restapi.Url;
|
||||||
import com.google.gerrit.reviewdb.client.Comment;
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.server.account.AccountLoader;
|
import com.google.gerrit.server.account.AccountLoader;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -55,100 +57,134 @@ class CommentJson {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommentInfo format(Comment c) throws OrmException {
|
public CommentFormatter newCommentFormatter() {
|
||||||
AccountLoader loader = null;
|
return new CommentFormatter();
|
||||||
if (fillAccounts) {
|
|
||||||
loader = accountLoaderFactory.create(true);
|
|
||||||
}
|
|
||||||
CommentInfo commentInfo = toCommentInfo(c, loader);
|
|
||||||
if (fillAccounts) {
|
|
||||||
loader.fill();
|
|
||||||
}
|
|
||||||
return commentInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, List<CommentInfo>> format(Iterable<Comment> l)
|
public RobotCommentFormatter newRobotCommentFormatter() {
|
||||||
throws OrmException {
|
return new RobotCommentFormatter();
|
||||||
Map<String, List<CommentInfo>> out = new TreeMap<>();
|
}
|
||||||
AccountLoader accountLoader = fillAccounts
|
|
||||||
? accountLoaderFactory.create(true)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
for (Comment c : l) {
|
private abstract class BaseCommentFormatter<F extends Comment,
|
||||||
CommentInfo o = toCommentInfo(c, accountLoader);
|
T extends CommentInfo> {
|
||||||
List<CommentInfo> list = out.get(o.path);
|
public T format(F comment) throws OrmException {
|
||||||
if (list == null) {
|
AccountLoader loader =
|
||||||
list = new ArrayList<>();
|
fillAccounts ? accountLoaderFactory.create(true) : null;
|
||||||
out.put(o.path, list);
|
T info = toInfo(comment, loader);
|
||||||
|
if (loader != null) {
|
||||||
|
loader.fill();
|
||||||
}
|
}
|
||||||
o.path = null;
|
return info;
|
||||||
list.add(o);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (List<CommentInfo> list : out.values()) {
|
public Map<String, List<T>> format(Iterable<F> comments)
|
||||||
Collections.sort(list, COMMENT_INFO_ORDER);
|
throws OrmException {
|
||||||
|
AccountLoader loader =
|
||||||
|
fillAccounts ? accountLoaderFactory.create(true) : null;
|
||||||
|
|
||||||
|
Map<String, List<T>> out = new TreeMap<>();
|
||||||
|
|
||||||
|
for (F c : comments) {
|
||||||
|
T o = toInfo(c, loader);
|
||||||
|
List<T> list = out.get(o.path);
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<>();
|
||||||
|
out.put(o.path, list);
|
||||||
|
}
|
||||||
|
o.path = null;
|
||||||
|
list.add(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (List<T> list : out.values()) {
|
||||||
|
Collections.sort(list, COMMENT_INFO_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader != null) {
|
||||||
|
loader.fill();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accountLoader != null) {
|
public List<T> formatAsList(Iterable<F> comments) throws OrmException {
|
||||||
accountLoader.fill();
|
AccountLoader loader =
|
||||||
|
fillAccounts ? accountLoaderFactory.create(true) : null;
|
||||||
|
|
||||||
|
List<T> out = FluentIterable.from(comments)
|
||||||
|
.transform(c -> toInfo(c, loader))
|
||||||
|
.toSortedList(COMMENT_INFO_ORDER);
|
||||||
|
|
||||||
|
if (loader != null) {
|
||||||
|
loader.fill();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
protected abstract T toInfo(F comment, AccountLoader loader);
|
||||||
}
|
|
||||||
|
|
||||||
List<CommentInfo> formatAsList(Iterable<Comment> l)
|
protected void fillCommentInfo(Comment c, CommentInfo r,
|
||||||
throws OrmException {
|
AccountLoader loader) {
|
||||||
AccountLoader accountLoader = fillAccounts
|
if (fillPatchSet) {
|
||||||
? accountLoaderFactory.create(true)
|
r.patchSet = c.key.patchSetId;
|
||||||
: null;
|
}
|
||||||
List<CommentInfo> out = FluentIterable
|
r.id = Url.encode(c.key.uuid);
|
||||||
.from(l)
|
r.path = c.key.filename;
|
||||||
.transform(c -> toCommentInfo(c, accountLoader))
|
if (c.side <= 0) {
|
||||||
.toSortedList(COMMENT_INFO_ORDER);
|
r.side = Side.PARENT;
|
||||||
|
if (c.side < 0) {
|
||||||
if (accountLoader != null) {
|
r.parent = -c.side;
|
||||||
accountLoader.fill();
|
}
|
||||||
}
|
}
|
||||||
|
if (c.lineNbr > 0) {
|
||||||
return out;
|
r.line = c.lineNbr;
|
||||||
}
|
}
|
||||||
|
r.inReplyTo = Url.encode(c.parentUuid);
|
||||||
private CommentInfo toCommentInfo(Comment c, AccountLoader loader) {
|
r.message = Strings.emptyToNull(c.message);
|
||||||
CommentInfo r = new CommentInfo();
|
r.updated = c.writtenOn;
|
||||||
if (fillPatchSet) {
|
r.range = toRange(c.range);
|
||||||
r.patchSet = c.key.patchSetId;
|
r.tag = c.tag;
|
||||||
}
|
if (loader != null) {
|
||||||
r.id = Url.encode(c.key.uuid);
|
r.author = loader.get(c.author.getId());
|
||||||
r.path = c.key.filename;
|
|
||||||
if (c.side <= 0) {
|
|
||||||
r.side = Side.PARENT;
|
|
||||||
if (c.side < 0) {
|
|
||||||
r.parent = -c.side;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c.lineNbr > 0) {
|
|
||||||
r.line = c.lineNbr;
|
private Range toRange(Comment.Range commentRange) {
|
||||||
|
Range range = null;
|
||||||
|
if (commentRange != null) {
|
||||||
|
range = new Range();
|
||||||
|
range.startLine = commentRange.startLine;
|
||||||
|
range.startCharacter = commentRange.startChar;
|
||||||
|
range.endLine = commentRange.endLine;
|
||||||
|
range.endCharacter = commentRange.endChar;
|
||||||
|
}
|
||||||
|
return range;
|
||||||
}
|
}
|
||||||
r.inReplyTo = Url.encode(c.parentUuid);
|
|
||||||
r.message = Strings.emptyToNull(c.message);
|
|
||||||
r.updated = c.writtenOn;
|
|
||||||
r.range = toRange(c.range);
|
|
||||||
r.tag = c.tag;
|
|
||||||
if (loader != null) {
|
|
||||||
r.author = loader.get(c.author.getId());
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Range toRange(Comment.Range commentRange) {
|
class CommentFormatter extends BaseCommentFormatter<Comment, CommentInfo> {
|
||||||
Range range = null;
|
@Override
|
||||||
if (commentRange != null) {
|
protected CommentInfo toInfo(Comment c, AccountLoader loader) {
|
||||||
range = new Range();
|
CommentInfo ci = new CommentInfo();
|
||||||
range.startLine = commentRange.startLine;
|
fillCommentInfo(c, ci, loader);
|
||||||
range.startCharacter = commentRange.startChar;
|
return ci;
|
||||||
range.endLine = commentRange.endLine;
|
}
|
||||||
range.endCharacter = commentRange.endChar;
|
|
||||||
|
private CommentFormatter() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RobotCommentFormatter
|
||||||
|
extends BaseCommentFormatter<RobotComment, RobotCommentInfo> {
|
||||||
|
@Override
|
||||||
|
protected RobotCommentInfo toInfo(RobotComment c, AccountLoader loader) {
|
||||||
|
RobotCommentInfo rci = new RobotCommentInfo();
|
||||||
|
rci.robotId = c.robotId;
|
||||||
|
rci.robotRunId = c.robotRunId;
|
||||||
|
rci.url = c.url;
|
||||||
|
fillCommentInfo(c, rci, loader);
|
||||||
|
return rci;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RobotCommentFormatter() {
|
||||||
}
|
}
|
||||||
return range;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -90,8 +90,8 @@ public class CreateDraftComment implements RestModifyView<RevisionResource, Draf
|
|||||||
Op op = new Op(rsrc.getPatchSet().getId(), in);
|
Op op = new Op(rsrc.getPatchSet().getId(), in);
|
||||||
bu.addOp(rsrc.getChange().getId(), op);
|
bu.addOp(rsrc.getChange().getId(), op);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
return Response.created(
|
return Response.created(commentJson.get().setFillAccounts(false)
|
||||||
commentJson.get().setFillAccounts(false).format(op.comment));
|
.newCommentFormatter().format(op.comment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,6 +33,6 @@ public class GetComment implements RestReadView<CommentResource> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommentInfo apply(CommentResource rsrc) throws OrmException {
|
public CommentInfo apply(CommentResource rsrc) throws OrmException {
|
||||||
return commentJson.get().format(rsrc.getComment());
|
return commentJson.get().newCommentFormatter().format(rsrc.getComment());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,6 @@ public class GetDraftComment implements RestReadView<DraftCommentResource> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommentInfo apply(DraftCommentResource rsrc) throws OrmException {
|
public CommentInfo apply(DraftCommentResource rsrc) throws OrmException {
|
||||||
return commentJson.get().format(rsrc.getComment());
|
return commentJson.get().newCommentFormatter().format(rsrc.getComment());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.extensions.common.RobotCommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class GetRobotComment implements RestReadView<RobotCommentResource> {
|
||||||
|
|
||||||
|
private final Provider<CommentJson> commentJson;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GetRobotComment(Provider<CommentJson> commentJson) {
|
||||||
|
this.commentJson = commentJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RobotCommentInfo apply(RobotCommentResource rsrc) throws OrmException {
|
||||||
|
return commentJson.get().newRobotCommentFormatter()
|
||||||
|
.format(rsrc.getComment());
|
||||||
|
}
|
||||||
|
}
|
@@ -53,6 +53,7 @@ public class ListChangeComments implements RestReadView<ChangeResource> {
|
|||||||
return commentJson.get()
|
return commentJson.get()
|
||||||
.setFillAccounts(true)
|
.setFillAccounts(true)
|
||||||
.setFillPatchSet(true)
|
.setFillPatchSet(true)
|
||||||
|
.newCommentFormatter()
|
||||||
.format(commentsUtil.publishedByChange(db.get(), cd.notes()));
|
.format(commentsUtil.publishedByChange(db.get(), cd.notes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,6 @@ public class ListChangeDrafts implements RestReadView<ChangeResource> {
|
|||||||
return commentJson.get()
|
return commentJson.get()
|
||||||
.setFillAccounts(false)
|
.setFillAccounts(false)
|
||||||
.setFillPatchSet(true)
|
.setFillPatchSet(true)
|
||||||
.format(drafts);
|
.newCommentFormatter().format(drafts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,13 +57,13 @@ public class ListRevisionDrafts implements RestReadView<RevisionResource> {
|
|||||||
throws OrmException {
|
throws OrmException {
|
||||||
return commentJson.get()
|
return commentJson.get()
|
||||||
.setFillAccounts(includeAuthorInfo())
|
.setFillAccounts(includeAuthorInfo())
|
||||||
.format(listComments(rsrc));
|
.newCommentFormatter().format(listComments(rsrc));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CommentInfo> getComments(RevisionResource rsrc)
|
public List<CommentInfo> getComments(RevisionResource rsrc)
|
||||||
throws OrmException {
|
throws OrmException {
|
||||||
return commentJson.get()
|
return commentJson.get()
|
||||||
.setFillAccounts(includeAuthorInfo())
|
.setFillAccounts(includeAuthorInfo())
|
||||||
.formatAsList(listComments(rsrc));
|
.newCommentFormatter().formatAsList(listComments(rsrc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.extensions.common.RobotCommentInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.CommentsUtil;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ListRobotComments implements RestReadView<RevisionResource> {
|
||||||
|
protected final Provider<ReviewDb> db;
|
||||||
|
protected final Provider<CommentJson> commentJson;
|
||||||
|
protected final CommentsUtil commentsUtil;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ListRobotComments(Provider<ReviewDb> db,
|
||||||
|
Provider<CommentJson> commentJson,
|
||||||
|
CommentsUtil commentsUtil) {
|
||||||
|
this.db = db;
|
||||||
|
this.commentJson = commentJson;
|
||||||
|
this.commentsUtil = commentsUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<RobotCommentInfo>> apply(RevisionResource rsrc)
|
||||||
|
throws OrmException {
|
||||||
|
return commentJson.get()
|
||||||
|
.setFillAccounts(true)
|
||||||
|
.newRobotCommentFormatter()
|
||||||
|
.format(listComments(rsrc));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RobotCommentInfo> getComments(RevisionResource rsrc)
|
||||||
|
throws OrmException {
|
||||||
|
return commentJson.get()
|
||||||
|
.setFillAccounts(true)
|
||||||
|
.newRobotCommentFormatter()
|
||||||
|
.formatAsList(listComments(rsrc));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Iterable<RobotComment> listComments(RevisionResource rsrc)
|
||||||
|
throws OrmException {
|
||||||
|
return commentsUtil.robotCommentsByPatchSet(
|
||||||
|
rsrc.getNotes(), rsrc.getPatchSet().getId());
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ import static com.google.gerrit.server.change.DraftCommentResource.DRAFT_COMMENT
|
|||||||
import static com.google.gerrit.server.change.FileResource.FILE_KIND;
|
import static com.google.gerrit.server.change.FileResource.FILE_KIND;
|
||||||
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
|
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
|
||||||
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
|
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
|
||||||
|
import static com.google.gerrit.server.change.RobotCommentResource.ROBOT_COMMENT_KIND;
|
||||||
import static com.google.gerrit.server.change.VoteResource.VOTE_KIND;
|
import static com.google.gerrit.server.change.VoteResource.VOTE_KIND;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||||
@@ -37,11 +38,13 @@ public class Module extends RestApiModule {
|
|||||||
bind(Reviewers.class);
|
bind(Reviewers.class);
|
||||||
bind(DraftComments.class);
|
bind(DraftComments.class);
|
||||||
bind(Comments.class);
|
bind(Comments.class);
|
||||||
|
bind(RobotComments.class);
|
||||||
bind(Files.class);
|
bind(Files.class);
|
||||||
bind(Votes.class);
|
bind(Votes.class);
|
||||||
|
|
||||||
DynamicMap.mapOf(binder(), CHANGE_KIND);
|
DynamicMap.mapOf(binder(), CHANGE_KIND);
|
||||||
DynamicMap.mapOf(binder(), COMMENT_KIND);
|
DynamicMap.mapOf(binder(), COMMENT_KIND);
|
||||||
|
DynamicMap.mapOf(binder(), ROBOT_COMMENT_KIND);
|
||||||
DynamicMap.mapOf(binder(), DRAFT_COMMENT_KIND);
|
DynamicMap.mapOf(binder(), DRAFT_COMMENT_KIND);
|
||||||
DynamicMap.mapOf(binder(), FILE_KIND);
|
DynamicMap.mapOf(binder(), FILE_KIND);
|
||||||
DynamicMap.mapOf(binder(), REVIEWER_KIND);
|
DynamicMap.mapOf(binder(), REVIEWER_KIND);
|
||||||
@@ -116,6 +119,9 @@ 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);
|
||||||
|
|
||||||
|
child(REVISION_KIND, "robotcomments").to(RobotComments.class);
|
||||||
|
get(ROBOT_COMMENT_KIND).to(GetRobotComment.class);
|
||||||
|
|
||||||
child(REVISION_KIND, "files").to(Files.class);
|
child(REVISION_KIND, "files").to(Files.class);
|
||||||
put(FILE_KIND, "reviewed").to(PutReviewed.class);
|
put(FILE_KIND, "reviewed").to(PutReviewed.class);
|
||||||
delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
|
delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
|
||||||
|
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
|
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
|
||||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.util.stream.Collectors.toSet;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
@@ -43,10 +44,12 @@ import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
|||||||
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;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewResult;
|
import com.google.gerrit.extensions.api.changes.ReviewResult;
|
||||||
import com.google.gerrit.extensions.client.Side;
|
import com.google.gerrit.extensions.client.Side;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
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.MethodNotAllowedException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
@@ -59,6 +62,7 @@ import com.google.gerrit.reviewdb.client.Patch;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
@@ -76,6 +80,7 @@ import com.google.gerrit.server.git.BatchUpdate.Context;
|
|||||||
import com.google.gerrit.server.git.UpdateException;
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
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.NotesMigration;
|
||||||
import com.google.gerrit.server.patch.PatchListCache;
|
import com.google.gerrit.server.patch.PatchListCache;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
@@ -94,7 +99,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -118,6 +122,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
private final CommentAdded commentAdded;
|
private final CommentAdded commentAdded;
|
||||||
private final PostReviewers postReviewers;
|
private final PostReviewers postReviewers;
|
||||||
private final String serverId;
|
private final String serverId;
|
||||||
|
private final NotesMigration migration;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostReview(Provider<ReviewDb> db,
|
PostReview(Provider<ReviewDb> db,
|
||||||
@@ -133,7 +138,8 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
EmailReviewComments.Factory email,
|
EmailReviewComments.Factory email,
|
||||||
CommentAdded commentAdded,
|
CommentAdded commentAdded,
|
||||||
PostReviewers postReviewers,
|
PostReviewers postReviewers,
|
||||||
@GerritServerId String serverId) {
|
@GerritServerId String serverId,
|
||||||
|
NotesMigration migration) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.batchUpdateFactory = batchUpdateFactory;
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
this.changes = changes;
|
this.changes = changes;
|
||||||
@@ -148,6 +154,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
this.commentAdded = commentAdded;
|
this.commentAdded = commentAdded;
|
||||||
this.postReviewers = postReviewers;
|
this.postReviewers = postReviewers;
|
||||||
this.serverId = serverId;
|
this.serverId = serverId;
|
||||||
|
this.migration = migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -173,6 +180,12 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
if (input.comments != null) {
|
if (input.comments != null) {
|
||||||
checkComments(revision, input.comments);
|
checkComments(revision, input.comments);
|
||||||
}
|
}
|
||||||
|
if (input.robotComments != null) {
|
||||||
|
if (!migration.readChanges()) {
|
||||||
|
throw new MethodNotAllowedException("robot comments not supported");
|
||||||
|
}
|
||||||
|
checkRobotComments(revision, input.robotComments);
|
||||||
|
}
|
||||||
if (input.notify == null) {
|
if (input.notify == null) {
|
||||||
log.warn("notify = null; assuming notify = NONE");
|
log.warn("notify = null; assuming notify = NONE");
|
||||||
input.notify = NotifyHandling.NONE;
|
input.notify = NotifyHandling.NONE;
|
||||||
@@ -316,16 +329,16 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkComments(RevisionResource revision, Map<String, List<CommentInput>> in)
|
private <T extends CommentInput> void checkComments(RevisionResource revision,
|
||||||
throws BadRequestException, OrmException {
|
Map<String, List<T>> in) throws BadRequestException, OrmException {
|
||||||
Iterator<Map.Entry<String, List<CommentInput>>> mapItr =
|
Iterator<? extends Map.Entry<String, List<T>>> mapItr =
|
||||||
in.entrySet().iterator();
|
in.entrySet().iterator();
|
||||||
Set<String> filePaths =
|
Set<String> filePaths =
|
||||||
Sets.newHashSet(changeDataFactory.create(
|
Sets.newHashSet(changeDataFactory.create(
|
||||||
db.get(), revision.getControl()).filePaths(
|
db.get(), revision.getControl()).filePaths(
|
||||||
revision.getPatchSet()));
|
revision.getPatchSet()));
|
||||||
while (mapItr.hasNext()) {
|
while (mapItr.hasNext()) {
|
||||||
Map.Entry<String, List<CommentInput>> ent = mapItr.next();
|
Map.Entry<String, List<T>> ent = mapItr.next();
|
||||||
String path = ent.getKey();
|
String path = ent.getKey();
|
||||||
if (!filePaths.contains(path) && !Patch.isMagic(path)) {
|
if (!filePaths.contains(path) && !Patch.isMagic(path)) {
|
||||||
throw new BadRequestException(String.format(
|
throw new BadRequestException(String.format(
|
||||||
@@ -333,7 +346,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
path, revision.getChange().currentPatchSetId()));
|
path, revision.getChange().currentPatchSetId()));
|
||||||
}
|
}
|
||||||
if (Patch.isMagic(path)) {
|
if (Patch.isMagic(path)) {
|
||||||
for (CommentInput comment : ent.getValue()) {
|
for (T comment : ent.getValue()) {
|
||||||
if (comment.side == Side.PARENT && comment.parent == null) {
|
if (comment.side == Side.PARENT && comment.parent == null) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
String.format("cannot comment on %s on auto-merge", path));
|
String.format("cannot comment on %s on auto-merge", path));
|
||||||
@@ -341,15 +354,15 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CommentInput> list = ent.getValue();
|
List<T> list = ent.getValue();
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
mapItr.remove();
|
mapItr.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<CommentInput> listItr = list.iterator();
|
Iterator<T> listItr = list.iterator();
|
||||||
while (listItr.hasNext()) {
|
while (listItr.hasNext()) {
|
||||||
CommentInput c = listItr.next();
|
T c = listItr.next();
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
listItr.remove();
|
listItr.remove();
|
||||||
continue;
|
continue;
|
||||||
@@ -370,6 +383,25 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkRobotComments(RevisionResource revision,
|
||||||
|
Map<String, List<RobotCommentInput>> in)
|
||||||
|
throws BadRequestException, OrmException {
|
||||||
|
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
|
||||||
|
String path = e.getKey();
|
||||||
|
for (RobotCommentInput c : e.getValue()) {
|
||||||
|
if (c.robotId == null) {
|
||||||
|
throw new BadRequestException(String
|
||||||
|
.format("robotId is missing for robot comment on %s", path));
|
||||||
|
}
|
||||||
|
if (c.robotRunId == null) {
|
||||||
|
throw new BadRequestException(String
|
||||||
|
.format("robotRunId is missing for robot comment on %s", path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkComments(revision, in);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to compare Comments with CommentInput comments.
|
* Used to compare Comments with CommentInput comments.
|
||||||
*/
|
*/
|
||||||
@@ -427,6 +459,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
|
ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
|
||||||
boolean dirty = false;
|
boolean dirty = false;
|
||||||
dirty |= insertComments(ctx);
|
dirty |= insertComments(ctx);
|
||||||
|
dirty |= insertRobotComments(ctx);
|
||||||
dirty |= updateLabels(ctx);
|
dirty |= updateLabels(ctx);
|
||||||
dirty |= insertMessage(ctx);
|
dirty |= insertMessage(ctx);
|
||||||
return dirty;
|
return dirty;
|
||||||
@@ -471,7 +504,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
|
|
||||||
Set<CommentSetEntry> existingIds = in.omitDuplicateComments
|
Set<CommentSetEntry> existingIds = in.omitDuplicateComments
|
||||||
? readExistingComments(ctx)
|
? readExistingComments(ctx)
|
||||||
: Collections.<CommentSetEntry>emptySet();
|
: Collections.emptySet();
|
||||||
|
|
||||||
for (Map.Entry<String, List<CommentInput>> ent : map.entrySet()) {
|
for (Map.Entry<String, List<CommentInput>> ent : map.entrySet()) {
|
||||||
String path = ent.getKey();
|
String path = ent.getKey();
|
||||||
@@ -530,14 +563,53 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
return !toDel.isEmpty() || !toPublish.isEmpty();
|
return !toDel.isEmpty() || !toPublish.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean insertRobotComments(ChangeContext ctx) throws OrmException {
|
||||||
|
if (in.robotComments == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RobotComment> toAdd = new ArrayList<>(in.robotComments.size());
|
||||||
|
|
||||||
|
Set<CommentSetEntry> existingIds = in.omitDuplicateComments
|
||||||
|
? readExistingRobotComments(ctx)
|
||||||
|
: Collections.emptySet();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<RobotCommentInput>> ent : in.robotComments.entrySet()) {
|
||||||
|
String path = ent.getKey();
|
||||||
|
for (RobotCommentInput c : ent.getValue()) {
|
||||||
|
RobotComment e = new RobotComment(
|
||||||
|
new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), path,
|
||||||
|
psId.get()),
|
||||||
|
user.getAccountId(), ctx.getWhen(), c.side(), c.message, serverId,
|
||||||
|
c.robotId, c.robotRunId);
|
||||||
|
e.parentUuid = Url.decode(c.inReplyTo);
|
||||||
|
e.url = c.url;
|
||||||
|
e.setLineNbrAndRange(c.line, c.range);
|
||||||
|
e.tag = in.tag;
|
||||||
|
setCommentRevId(e, patchListCache, ctx.getChange(), ps);
|
||||||
|
|
||||||
|
if (existingIds.contains(CommentSetEntry.create(e))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
toAdd.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsUtil.putRobotComments(ctx.getUpdate(psId), toAdd);
|
||||||
|
comments.addAll(toAdd);
|
||||||
|
return !toAdd.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
private Set<CommentSetEntry> readExistingComments(ChangeContext ctx)
|
private Set<CommentSetEntry> readExistingComments(ChangeContext ctx)
|
||||||
throws OrmException {
|
throws OrmException {
|
||||||
Set<CommentSetEntry> r = new HashSet<>();
|
return commentsUtil.publishedByChange(ctx.getDb(), ctx.getNotes())
|
||||||
for (Comment c : commentsUtil.publishedByChange(ctx.getDb(),
|
.stream().map(CommentSetEntry::create).collect(toSet());
|
||||||
ctx.getNotes())) {
|
}
|
||||||
r.add(CommentSetEntry.create(c));
|
|
||||||
}
|
private Set<CommentSetEntry> readExistingRobotComments(ChangeContext ctx)
|
||||||
return r;
|
throws OrmException {
|
||||||
|
return commentsUtil.robotCommentsByChange(ctx.getNotes())
|
||||||
|
.stream().map(CommentSetEntry::create).collect(toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Comment> changeDrafts(ChangeContext ctx)
|
private Map<String, Comment> changeDrafts(ChangeContext ctx)
|
||||||
|
@@ -92,8 +92,9 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
|
|||||||
Op op = new Op(rsrc.getComment().key, in);
|
Op op = new Op(rsrc.getComment().key, in);
|
||||||
bu.addOp(rsrc.getChange().getId(), op);
|
bu.addOp(rsrc.getChange().getId(), op);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
return Response.ok(
|
return Response.ok(commentJson.get()
|
||||||
commentJson.get().setFillAccounts(false).format(op.comment));
|
.setFillAccounts(false)
|
||||||
|
.newCommentFormatter().format(op.comment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.extensions.restapi.RestResource;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestView;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
|
public class RobotCommentResource implements RestResource {
|
||||||
|
public static final TypeLiteral<RestView<RobotCommentResource>> ROBOT_COMMENT_KIND =
|
||||||
|
new TypeLiteral<RestView<RobotCommentResource>>() {};
|
||||||
|
|
||||||
|
private final RevisionResource rev;
|
||||||
|
private final RobotComment comment;
|
||||||
|
|
||||||
|
public RobotCommentResource(RevisionResource rev, RobotComment c) {
|
||||||
|
this.rev = rev;
|
||||||
|
this.comment = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchSet getPatchSet() {
|
||||||
|
return rev.getPatchSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
RobotComment getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getId() {
|
||||||
|
return comment.key.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Account.Id getAuthorId() {
|
||||||
|
return comment.author.getId();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.extensions.registration.DynamicMap;
|
||||||
|
import com.google.gerrit.extensions.restapi.ChildCollection;
|
||||||
|
import com.google.gerrit.extensions.restapi.IdString;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestView;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
|
import com.google.gerrit.server.CommentsUtil;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class RobotComments
|
||||||
|
implements ChildCollection<RevisionResource, RobotCommentResource> {
|
||||||
|
private final DynamicMap<RestView<RobotCommentResource>> views;
|
||||||
|
private final ListRobotComments list;
|
||||||
|
private final CommentsUtil commentsUtil;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RobotComments(DynamicMap<RestView<RobotCommentResource>> views,
|
||||||
|
ListRobotComments list,
|
||||||
|
CommentsUtil commentsUtil) {
|
||||||
|
this.views = views;
|
||||||
|
this.list = list;
|
||||||
|
this.commentsUtil = commentsUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicMap<RestView<RobotCommentResource>> views() {
|
||||||
|
return views;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListRobotComments list() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RobotCommentResource parse(RevisionResource rev, IdString id)
|
||||||
|
throws ResourceNotFoundException, OrmException {
|
||||||
|
String uuid = id.get();
|
||||||
|
ChangeNotes notes = rev.getNotes();
|
||||||
|
|
||||||
|
for (RobotComment c : commentsUtil.robotCommentsByPatchSet(
|
||||||
|
notes, rev.getPatchSet().getId())) {
|
||||||
|
if (uuid.equals(c.key.uuid)) {
|
||||||
|
return new RobotCommentResource(rev, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ResourceNotFoundException(id);
|
||||||
|
}
|
||||||
|
}
|
@@ -24,6 +24,7 @@ import com.google.gerrit.reviewdb.client.Change;
|
|||||||
import com.google.gerrit.reviewdb.client.Comment;
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
import com.google.gerrit.reviewdb.client.Patch;
|
import com.google.gerrit.reviewdb.client.Patch;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.server.CommentsUtil;
|
import com.google.gerrit.server.CommentsUtil;
|
||||||
import com.google.gerrit.server.patch.PatchFile;
|
import com.google.gerrit.server.patch.PatchFile;
|
||||||
import com.google.gerrit.server.patch.PatchList;
|
import com.google.gerrit.server.patch.PatchList;
|
||||||
@@ -165,6 +166,14 @@ public class CommentSender extends ReplyToChangeSender {
|
|||||||
|
|
||||||
private void appendComment(StringBuilder out, int contextLines,
|
private void appendComment(StringBuilder out, int contextLines,
|
||||||
PatchFile currentFileData, Comment comment) {
|
PatchFile currentFileData, Comment comment) {
|
||||||
|
if (comment instanceof RobotComment) {
|
||||||
|
RobotComment robotComment = (RobotComment) comment;
|
||||||
|
out.append("Robot Comment from ")
|
||||||
|
.append(robotComment.robotId)
|
||||||
|
.append(" (run ID ")
|
||||||
|
.append(robotComment.robotRunId)
|
||||||
|
.append("):\n");
|
||||||
|
}
|
||||||
short side = comment.side;
|
short side = comment.side;
|
||||||
Comment.Range range = comment.range;
|
Comment.Range range = comment.range;
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
|
@@ -138,7 +138,7 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
|
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
|
||||||
ObjectId curr, CommitBuilder cb)
|
ObjectId curr, CommitBuilder cb)
|
||||||
throws ConfigInvalidException, OrmException, IOException {
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
|
RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr);
|
||||||
Set<RevId> updatedRevs =
|
Set<RevId> updatedRevs =
|
||||||
Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
|
Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
|
||||||
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
||||||
@@ -158,7 +158,7 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
updatedRevs.add(e.getKey());
|
updatedRevs.add(e.getKey());
|
||||||
ObjectId id = ObjectId.fromString(e.getKey().get());
|
ObjectId id = ObjectId.fromString(e.getKey().get());
|
||||||
byte[] data = e.getValue().build(noteUtil);
|
byte[] data = e.getValue().build(noteUtil, noteUtil.getWriteJson());
|
||||||
if (!Arrays.equals(data, e.getValue().baseRaw)) {
|
if (!Arrays.equals(data, e.getValue().baseRaw)) {
|
||||||
touchedAnyRevs = true;
|
touchedAnyRevs = true;
|
||||||
}
|
}
|
||||||
@@ -189,8 +189,8 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
|
private RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap(RevWalk rw,
|
||||||
throws ConfigInvalidException, OrmException, IOException {
|
ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
|
||||||
if (migration.readChanges()) {
|
if (migration.readChanges()) {
|
||||||
// If reading from changes is enabled, then the old DraftCommentNotes
|
// If reading from changes is enabled, then the old DraftCommentNotes
|
||||||
// already parsed the revision notes. We can reuse them as long as the ref
|
// already parsed the revision notes. We can reuse them as long as the ref
|
||||||
@@ -202,7 +202,8 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
if (draftNotes != null) {
|
if (draftNotes != null) {
|
||||||
ObjectId idFromNotes =
|
ObjectId idFromNotes =
|
||||||
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
|
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
|
||||||
RevisionNoteMap rnm = draftNotes.getRevisionNoteMap();
|
RevisionNoteMap<ChangeRevisionNote> rnm =
|
||||||
|
draftNotes.getRevisionNoteMap();
|
||||||
if (idFromNotes.equals(curr) && rnm != null) {
|
if (idFromNotes.equals(curr) && rnm != null) {
|
||||||
return rnm;
|
return rnm;
|
||||||
}
|
}
|
||||||
|
@@ -45,6 +45,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
@@ -339,10 +340,11 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
|
|
||||||
// Parsed note map state, used by ChangeUpdate to make in-place editing of
|
// Parsed note map state, used by ChangeUpdate to make in-place editing of
|
||||||
// notes easier.
|
// notes easier.
|
||||||
RevisionNoteMap revisionNoteMap;
|
RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
|
||||||
|
|
||||||
private NoteDbUpdateManager.Result rebuildResult;
|
private NoteDbUpdateManager.Result rebuildResult;
|
||||||
private DraftCommentNotes draftCommentNotes;
|
private DraftCommentNotes draftCommentNotes;
|
||||||
|
private RobotCommentNotes robotCommentNotes;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public ChangeNotes(Args args, Change change) {
|
public ChangeNotes(Args args, Change change) {
|
||||||
@@ -448,6 +450,12 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
filtered);
|
filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImmutableListMultimap<RevId, RobotComment> getRobotComments()
|
||||||
|
throws OrmException {
|
||||||
|
loadRobotComments();
|
||||||
|
return robotCommentNotes.getComments();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If draft comments have already been loaded for this author, then they will
|
* If draft comments have already been loaded for this author, then they will
|
||||||
* not be reloaded. However, this method will load the comments if no draft
|
* not be reloaded. However, this method will load the comments if no draft
|
||||||
@@ -464,11 +472,22 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadRobotComments() throws OrmException {
|
||||||
|
if (robotCommentNotes == null) {
|
||||||
|
robotCommentNotes = new RobotCommentNotes(args, change);
|
||||||
|
robotCommentNotes.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
DraftCommentNotes getDraftCommentNotes() {
|
DraftCommentNotes getDraftCommentNotes() {
|
||||||
return draftCommentNotes;
|
return draftCommentNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RobotCommentNotes getRobotCommentNotes() {
|
||||||
|
return robotCommentNotes;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean containsComment(Comment c) throws OrmException {
|
public boolean containsComment(Comment c) throws OrmException {
|
||||||
if (containsCommentPublished(c)) {
|
if (containsCommentPublished(c)) {
|
||||||
return true;
|
return true;
|
||||||
|
@@ -73,14 +73,14 @@ public class ChangeNotesCache {
|
|||||||
* used as an optimization; {@link ChangeNotes} is capable of lazily loading
|
* used as an optimization; {@link ChangeNotes} is capable of lazily loading
|
||||||
* it as necessary.
|
* it as necessary.
|
||||||
*/
|
*/
|
||||||
@Nullable abstract RevisionNoteMap revisionNoteMap();
|
@Nullable abstract RevisionNoteMap<ChangeRevisionNote> revisionNoteMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Loader implements Callable<ChangeNotesState> {
|
private class Loader implements Callable<ChangeNotesState> {
|
||||||
private final Key key;
|
private final Key key;
|
||||||
private final ChangeNotesRevWalk rw;
|
private final ChangeNotesRevWalk rw;
|
||||||
|
|
||||||
private RevisionNoteMap revisionNoteMap;
|
private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
|
||||||
|
|
||||||
private Loader(Key key, ChangeNotesRevWalk rw) {
|
private Loader(Key key, ChangeNotesRevWalk rw) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
@@ -149,7 +149,7 @@ class ChangeNotesParser {
|
|||||||
private String submissionId;
|
private String submissionId;
|
||||||
private String tag;
|
private String tag;
|
||||||
private PatchSet.Id currentPatchSetId;
|
private PatchSet.Id currentPatchSetId;
|
||||||
private RevisionNoteMap revisionNoteMap;
|
private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
|
||||||
|
|
||||||
ChangeNotesParser(Change.Id changeId, ObjectId tip, ChangeNotesRevWalk walk,
|
ChangeNotesParser(Change.Id changeId, ObjectId tip, ChangeNotesRevWalk walk,
|
||||||
ChangeNoteUtil noteUtil, NoteDbMetrics metrics) {
|
ChangeNoteUtil noteUtil, NoteDbMetrics metrics) {
|
||||||
@@ -195,7 +195,7 @@ class ChangeNotesParser {
|
|||||||
return buildState();
|
return buildState();
|
||||||
}
|
}
|
||||||
|
|
||||||
RevisionNoteMap getRevisionNoteMap() {
|
RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap() {
|
||||||
return revisionNoteMap;
|
return revisionNoteMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,18 +630,18 @@ class ChangeNotesParser {
|
|||||||
revisionNoteMap = RevisionNoteMap.parse(
|
revisionNoteMap = RevisionNoteMap.parse(
|
||||||
noteUtil, id, reader, NoteMap.read(reader, tipCommit),
|
noteUtil, id, reader, NoteMap.read(reader, tipCommit),
|
||||||
PatchLineComment.Status.PUBLISHED);
|
PatchLineComment.Status.PUBLISHED);
|
||||||
Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes;
|
Map<RevId, ChangeRevisionNote> rns = revisionNoteMap.revisionNotes;
|
||||||
|
|
||||||
for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) {
|
for (Map.Entry<RevId, ChangeRevisionNote> e : rns.entrySet()) {
|
||||||
for (Comment c : e.getValue().comments) {
|
for (Comment c : e.getValue().getComments()) {
|
||||||
comments.put(e.getKey(), c);
|
comments.put(e.getKey(), c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PatchSet ps : patchSets.values()) {
|
for (PatchSet ps : patchSets.values()) {
|
||||||
RevisionNote rn = rns.get(ps.getRevision());
|
ChangeRevisionNote rn = rns.get(ps.getRevision());
|
||||||
if (rn != null && rn.pushCert != null) {
|
if (rn != null && rn.getPushCert() != null) {
|
||||||
ps.setPushCertificate(rn.pushCert);
|
ps.setPushCertificate(rn.getPushCert());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (C) 2016 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 java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.util.MutableInteger;
|
||||||
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class ChangeRevisionNote extends RevisionNote<Comment> {
|
||||||
|
private static final byte[] CERT_HEADER =
|
||||||
|
"certificate version ".getBytes(UTF_8);
|
||||||
|
// See org.eclipse.jgit.transport.PushCertificateParser.END_SIGNATURE
|
||||||
|
private static final byte[] END_SIGNATURE =
|
||||||
|
"-----END PGP SIGNATURE-----\n".getBytes(UTF_8);
|
||||||
|
|
||||||
|
private final ChangeNoteUtil noteUtil;
|
||||||
|
private final Change.Id changeId;
|
||||||
|
private final PatchLineComment.Status status;
|
||||||
|
private String pushCert = null;
|
||||||
|
|
||||||
|
ChangeRevisionNote(ChangeNoteUtil noteUtil, Change.Id changeId,
|
||||||
|
ObjectReader reader, ObjectId noteId, PatchLineComment.Status status) {
|
||||||
|
super(reader, noteId);
|
||||||
|
this.noteUtil = noteUtil;
|
||||||
|
this.changeId = changeId;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPushCert() {
|
||||||
|
checkParsed();
|
||||||
|
return pushCert;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Comment> parse(byte[] raw, int offset)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
MutableInteger p = new MutableInteger();
|
||||||
|
p.value = offset;
|
||||||
|
|
||||||
|
if (isJson(raw, p.value)) {
|
||||||
|
RevisionNoteData data = parseJson(noteUtil, raw, p.value);
|
||||||
|
if (status == PatchLineComment.Status.PUBLISHED) {
|
||||||
|
pushCert = data.pushCert;
|
||||||
|
} else {
|
||||||
|
pushCert = null;
|
||||||
|
}
|
||||||
|
return data.comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == PatchLineComment.Status.PUBLISHED) {
|
||||||
|
pushCert = parsePushCert(changeId, raw, p);
|
||||||
|
trimLeadingEmptyLines(raw, p);
|
||||||
|
} else {
|
||||||
|
pushCert = null;
|
||||||
|
}
|
||||||
|
return noteUtil.parseNote(raw, p, changeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isJson(byte[] raw, int offset) {
|
||||||
|
return raw[offset] == '{' || raw[offset] == '[';
|
||||||
|
}
|
||||||
|
|
||||||
|
private RevisionNoteData parseJson(ChangeNoteUtil noteUtil, byte[] raw,
|
||||||
|
int offset) throws IOException {
|
||||||
|
try (InputStream is = new ByteArrayInputStream(
|
||||||
|
raw, offset, raw.length - offset);
|
||||||
|
Reader r = new InputStreamReader(is)) {
|
||||||
|
return noteUtil.getGson().fromJson(r, RevisionNoteData.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parsePushCert(Change.Id changeId, byte[] bytes,
|
||||||
|
MutableInteger p) throws ConfigInvalidException {
|
||||||
|
if (RawParseUtils.match(bytes, p.value, CERT_HEADER) < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int end = Bytes.indexOf(bytes, END_SIGNATURE);
|
||||||
|
if (end < 0) {
|
||||||
|
throw ChangeNotes.parseException(
|
||||||
|
changeId, "invalid push certificate in note");
|
||||||
|
}
|
||||||
|
int start = p.value;
|
||||||
|
p.value = end + END_SIGNATURE.length;
|
||||||
|
return new String(bytes, start, p.value);
|
||||||
|
}
|
||||||
|
}
|
@@ -51,6 +51,7 @@ import com.google.gerrit.reviewdb.client.Comment;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
@@ -110,6 +111,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
|
|
||||||
private final AccountCache accountCache;
|
private final AccountCache accountCache;
|
||||||
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
||||||
|
private final RobotCommentUpdate.Factory robotCommentUpdateFactory;
|
||||||
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
|
|
||||||
private final Table<String, Account.Id, Optional<Short>> approvals;
|
private final Table<String, Account.Id, Optional<Short>> approvals;
|
||||||
@@ -135,6 +137,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
private boolean isAllowWriteToNewtRef;
|
private boolean isAllowWriteToNewtRef;
|
||||||
|
|
||||||
private ChangeDraftUpdate draftUpdate;
|
private ChangeDraftUpdate draftUpdate;
|
||||||
|
private RobotCommentUpdate robotCommentUpdate;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
private ChangeUpdate(
|
private ChangeUpdate(
|
||||||
@@ -144,11 +147,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
ChangeNoteUtil noteUtil) {
|
ChangeNoteUtil noteUtil) {
|
||||||
this(serverIdent, anonymousCowardName, migration, accountCache,
|
this(serverIdent, anonymousCowardName, migration, accountCache,
|
||||||
updateManagerFactory, draftUpdateFactory,
|
updateManagerFactory, draftUpdateFactory, robotCommentUpdateFactory,
|
||||||
projectCache, ctl, serverIdent.getWhen(), noteUtil);
|
projectCache, ctl, serverIdent.getWhen(), noteUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,13 +164,14 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
ChangeNoteUtil noteUtil) {
|
ChangeNoteUtil noteUtil) {
|
||||||
this(serverIdent, anonymousCowardName, migration, accountCache,
|
this(serverIdent, anonymousCowardName, migration, accountCache,
|
||||||
updateManagerFactory, draftUpdateFactory, ctl,
|
updateManagerFactory, draftUpdateFactory, robotCommentUpdateFactory,
|
||||||
when,
|
ctl, when,
|
||||||
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
||||||
noteUtil);
|
noteUtil);
|
||||||
}
|
}
|
||||||
@@ -188,6 +193,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
@Assisted Comparator<String> labelNameComparator,
|
@Assisted Comparator<String> labelNameComparator,
|
||||||
@@ -196,6 +202,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
anonymousCowardName, noteUtil, when);
|
anonymousCowardName, noteUtil, when);
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
this.draftUpdateFactory = draftUpdateFactory;
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
|
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
||||||
this.updateManagerFactory = updateManagerFactory;
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
this.approvals = approvals(labelNameComparator);
|
this.approvals = approvals(labelNameComparator);
|
||||||
}
|
}
|
||||||
@@ -208,6 +215,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
NoteDbUpdateManager.Factory updateManagerFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
|
RobotCommentUpdate.Factory robotCommentUpdateFactory,
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteUtil noteUtil,
|
||||||
@Assisted Change change,
|
@Assisted Change change,
|
||||||
@Assisted @Nullable Account.Id accountId,
|
@Assisted @Nullable Account.Id accountId,
|
||||||
@@ -218,6 +226,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
accountId, authorIdent, when);
|
accountId, authorIdent, when);
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
this.draftUpdateFactory = draftUpdateFactory;
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
|
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
|
||||||
this.updateManagerFactory = updateManagerFactory;
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
this.approvals = approvals(labelNameComparator);
|
this.approvals = approvals(labelNameComparator);
|
||||||
}
|
}
|
||||||
@@ -320,6 +329,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putRobotComment(RobotComment c) {
|
||||||
|
verifyComment(c);
|
||||||
|
createRobotCommentUpdateIfNull();
|
||||||
|
robotCommentUpdate.putComment(c);
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteComment(Comment c) {
|
public void deleteComment(Comment c) {
|
||||||
verifyComment(c);
|
verifyComment(c);
|
||||||
createDraftUpdateIfNull().deleteComment(c);
|
createDraftUpdateIfNull().deleteComment(c);
|
||||||
@@ -340,6 +355,21 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
return draftUpdate;
|
return draftUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
RobotCommentUpdate createRobotCommentUpdateIfNull() {
|
||||||
|
if (robotCommentUpdate == null) {
|
||||||
|
ChangeNotes notes = getNotes();
|
||||||
|
if (notes != null) {
|
||||||
|
robotCommentUpdate =
|
||||||
|
robotCommentUpdateFactory.create(notes, accountId, authorIdent, when);
|
||||||
|
} else {
|
||||||
|
robotCommentUpdate = robotCommentUpdateFactory.create(
|
||||||
|
getChange(), accountId, authorIdent, when);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return robotCommentUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
private void verifyComment(Comment c) {
|
private void verifyComment(Comment c) {
|
||||||
checkArgument(c.revId != null, "RevId required for comment: %s", c);
|
checkArgument(c.revId != null, "RevId required for comment: %s", c);
|
||||||
checkArgument(c.author.getId().equals(getAccountId()),
|
checkArgument(c.author.getId().equals(getAccountId()),
|
||||||
@@ -415,7 +445,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
if (comments.isEmpty() && pushCert == null) {
|
if (comments.isEmpty() && pushCert == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
|
RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr);
|
||||||
|
|
||||||
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
||||||
for (Comment c : comments) {
|
for (Comment c : comments) {
|
||||||
@@ -431,15 +461,15 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
|
|
||||||
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
ObjectId data = inserter.insert(
|
ObjectId data = inserter.insert(
|
||||||
OBJ_BLOB, e.getValue().build(noteUtil));
|
OBJ_BLOB, e.getValue().build(noteUtil, noteUtil.getWriteJson()));
|
||||||
rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rnm.noteMap.writeTree(inserter);
|
return rnm.noteMap.writeTree(inserter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
|
private RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap(RevWalk rw,
|
||||||
throws ConfigInvalidException, OrmException, IOException {
|
ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
|
||||||
if (curr.equals(ObjectId.zeroId())) {
|
if (curr.equals(ObjectId.zeroId())) {
|
||||||
return RevisionNoteMap.emptyMap();
|
return RevisionNoteMap.emptyMap();
|
||||||
}
|
}
|
||||||
@@ -467,12 +497,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
PatchLineComment.Status.PUBLISHED);
|
PatchLineComment.Status.PUBLISHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkComments(Map<RevId, RevisionNote> existingNotes,
|
private void checkComments(Map<RevId, ChangeRevisionNote> existingNotes,
|
||||||
Map<RevId, RevisionNoteBuilder> toUpdate) throws OrmException {
|
Map<RevId, RevisionNoteBuilder> toUpdate) throws OrmException {
|
||||||
// Prohibit various kinds of illegal operations on comments.
|
// Prohibit various kinds of illegal operations on comments.
|
||||||
Set<Comment.Key> existing = new HashSet<>();
|
Set<Comment.Key> existing = new HashSet<>();
|
||||||
for (RevisionNote rn : existingNotes.values()) {
|
for (ChangeRevisionNote rn : existingNotes.values()) {
|
||||||
for (Comment c : rn.comments) {
|
for (Comment c : rn.getComments()) {
|
||||||
existing.add(c.key);
|
existing.add(c.key);
|
||||||
if (draftUpdate != null) {
|
if (draftUpdate != null) {
|
||||||
// Take advantage of an existing update on All-Users to prune any
|
// Take advantage of an existing update on All-Users to prune any
|
||||||
@@ -677,6 +707,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
return draftUpdate;
|
return draftUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RobotCommentUpdate getRobotCommentUpdate() {
|
||||||
|
return robotCommentUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
public void setAllowWriteToNewRef(boolean allow) {
|
public void setAllowWriteToNewRef(boolean allow) {
|
||||||
isAllowWriteToNewtRef = allow;
|
isAllowWriteToNewtRef = allow;
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
private final NoteDbUpdateManager.Result rebuildResult;
|
private final NoteDbUpdateManager.Result rebuildResult;
|
||||||
|
|
||||||
private ImmutableListMultimap<RevId, Comment> comments;
|
private ImmutableListMultimap<RevId, Comment> comments;
|
||||||
private RevisionNoteMap revisionNoteMap;
|
private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
DraftCommentNotes(
|
DraftCommentNotes(
|
||||||
@@ -103,7 +103,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
this.rebuildResult = rebuildResult;
|
this.rebuildResult = rebuildResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
RevisionNoteMap getRevisionNoteMap() {
|
RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap() {
|
||||||
return revisionNoteMap;
|
return revisionNoteMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +144,8 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
args.noteUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit),
|
args.noteUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit),
|
||||||
PatchLineComment.Status.DRAFT);
|
PatchLineComment.Status.DRAFT);
|
||||||
Multimap<RevId, Comment> cs = ArrayListMultimap.create();
|
Multimap<RevId, Comment> cs = ArrayListMultimap.create();
|
||||||
for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) {
|
for (ChangeRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
|
||||||
for (Comment c : rn.comments) {
|
for (Comment c : rn.getComments()) {
|
||||||
cs.put(new RevId(c.revId), c);
|
cs.put(new RevId(c.revId), c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,8 @@ public class NoteDbModule extends FactoryModule {
|
|||||||
factory(ChangeUpdate.Factory.class);
|
factory(ChangeUpdate.Factory.class);
|
||||||
factory(ChangeDraftUpdate.Factory.class);
|
factory(ChangeDraftUpdate.Factory.class);
|
||||||
factory(DraftCommentNotes.Factory.class);
|
factory(DraftCommentNotes.Factory.class);
|
||||||
|
factory(RobotCommentUpdate.Factory.class);
|
||||||
|
factory(RobotCommentNotes.Factory.class);
|
||||||
factory(NoteDbUpdateManager.Factory.class);
|
factory(NoteDbUpdateManager.Factory.class);
|
||||||
if (!useTestBindings) {
|
if (!useTestBindings) {
|
||||||
install(ChangeNotesCache.module());
|
install(ChangeNotesCache.module());
|
||||||
|
@@ -179,6 +179,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
private final Project.NameKey projectName;
|
private final Project.NameKey projectName;
|
||||||
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 Set<Change.Id> toDelete;
|
private final Set<Change.Id> toDelete;
|
||||||
|
|
||||||
private OpenRepo changeRepo;
|
private OpenRepo changeRepo;
|
||||||
@@ -199,6 +200,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
this.projectName = projectName;
|
this.projectName = projectName;
|
||||||
changeUpdates = ArrayListMultimap.create();
|
changeUpdates = ArrayListMultimap.create();
|
||||||
draftUpdates = ArrayListMultimap.create();
|
draftUpdates = ArrayListMultimap.create();
|
||||||
|
robotCommentUpdates = ArrayListMultimap.create();
|
||||||
toDelete = new HashSet<>();
|
toDelete = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +275,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
return changeUpdates.isEmpty()
|
return changeUpdates.isEmpty()
|
||||||
&& draftUpdates.isEmpty()
|
&& draftUpdates.isEmpty()
|
||||||
|
&& robotCommentUpdates.isEmpty()
|
||||||
&& toDelete.isEmpty();
|
&& toDelete.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +297,10 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
if (du != null) {
|
if (du != null) {
|
||||||
draftUpdates.put(du.getRefName(), du);
|
draftUpdates.put(du.getRefName(), du);
|
||||||
}
|
}
|
||||||
|
RobotCommentUpdate rcu = update.getRobotCommentUpdate();
|
||||||
|
if (rcu != null) {
|
||||||
|
robotCommentUpdates.put(rcu.getRefName(), rcu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(ChangeDraftUpdate draftUpdate) {
|
public void add(ChangeDraftUpdate draftUpdate) {
|
||||||
@@ -453,6 +460,9 @@ public class NoteDbUpdateManager implements AutoCloseable {
|
|||||||
if (!draftUpdates.isEmpty()) {
|
if (!draftUpdates.isEmpty()) {
|
||||||
addUpdates(draftUpdates, allUsersRepo);
|
addUpdates(draftUpdates, allUsersRepo);
|
||||||
}
|
}
|
||||||
|
if (!robotCommentUpdates.isEmpty()) {
|
||||||
|
addUpdates(robotCommentUpdates, changeRepo);
|
||||||
|
}
|
||||||
for (Change.Id id : toDelete) {
|
for (Change.Id id : toDelete) {
|
||||||
doDelete(id);
|
doDelete(id);
|
||||||
}
|
}
|
||||||
|
@@ -14,106 +14,66 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
|
||||||
import com.google.gerrit.reviewdb.client.Comment;
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.util.MutableInteger;
|
import org.eclipse.jgit.util.MutableInteger;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.util.List;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
class RevisionNote {
|
abstract class RevisionNote<T extends Comment> {
|
||||||
static final int MAX_NOTE_SZ = 25 << 20;
|
static final int MAX_NOTE_SZ = 25 << 20;
|
||||||
|
|
||||||
private static final byte[] CERT_HEADER =
|
protected static void trimLeadingEmptyLines(byte[] bytes, MutableInteger p) {
|
||||||
"certificate version ".getBytes(UTF_8);
|
|
||||||
// See org.eclipse.jgit.transport.PushCertificateParser.END_SIGNATURE
|
|
||||||
private static final byte[] END_SIGNATURE =
|
|
||||||
"-----END PGP SIGNATURE-----\n".getBytes(UTF_8);
|
|
||||||
|
|
||||||
private static void trimLeadingEmptyLines(byte[] bytes, MutableInteger p) {
|
|
||||||
while (p.value < bytes.length && bytes[p.value] == '\n') {
|
while (p.value < bytes.length && bytes[p.value] == '\n') {
|
||||||
p.value++;
|
p.value++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parsePushCert(Change.Id changeId, byte[] bytes,
|
private final ObjectReader reader;
|
||||||
MutableInteger p) throws ConfigInvalidException {
|
private final ObjectId noteId;
|
||||||
if (RawParseUtils.match(bytes, p.value, CERT_HEADER) < 0) {
|
|
||||||
return null;
|
private byte[] raw;
|
||||||
}
|
private ImmutableList<T> comments;
|
||||||
int end = Bytes.indexOf(bytes, END_SIGNATURE);
|
|
||||||
if (end < 0) {
|
RevisionNote(ObjectReader reader, ObjectId noteId) {
|
||||||
throw ChangeNotes.parseException(
|
this.reader = reader;
|
||||||
changeId, "invalid push certificate in note");
|
this.noteId = noteId;
|
||||||
}
|
|
||||||
int start = p.value;
|
|
||||||
p.value = end + END_SIGNATURE.length;
|
|
||||||
return new String(bytes, start, p.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] raw;
|
public byte[] getRaw() {
|
||||||
final ImmutableList<Comment> comments;
|
checkParsed();
|
||||||
final String pushCert;
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
RevisionNote(ChangeNoteUtil noteUtil, Change.Id changeId,
|
public ImmutableList<T> getComments() {
|
||||||
ObjectReader reader, ObjectId noteId, PatchLineComment.Status status)
|
checkParsed();
|
||||||
throws ConfigInvalidException, IOException {
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse() throws IOException, ConfigInvalidException {
|
||||||
raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
|
raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
|
||||||
MutableInteger p = new MutableInteger();
|
MutableInteger p = new MutableInteger();
|
||||||
trimLeadingEmptyLines(raw, p);
|
trimLeadingEmptyLines(raw, p);
|
||||||
if (p.value >= raw.length) {
|
if (p.value >= raw.length) {
|
||||||
comments = null;
|
comments = null;
|
||||||
pushCert = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJson(raw, p.value)) {
|
comments = ImmutableList.copyOf(parse(raw, p.value));
|
||||||
RevisionNoteData data = parseJson(noteUtil, p.value);
|
|
||||||
comments = ImmutableList.copyOf(data.comments);
|
|
||||||
if (status == PatchLineComment.Status.PUBLISHED) {
|
|
||||||
pushCert = data.pushCert;
|
|
||||||
} else {
|
|
||||||
pushCert = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == PatchLineComment.Status.PUBLISHED) {
|
|
||||||
pushCert = parsePushCert(changeId, raw, p);
|
|
||||||
trimLeadingEmptyLines(raw, p);
|
|
||||||
} else {
|
|
||||||
pushCert = null;
|
|
||||||
}
|
|
||||||
comments = ImmutableList.copyOf(noteUtil.parseNote(raw, p, changeId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isJson(byte[] raw, int offset) {
|
protected abstract List<T> parse(byte[] raw, int offset)
|
||||||
return raw[offset] == '{' || raw[offset] == '[';
|
throws IOException, ConfigInvalidException;
|
||||||
}
|
|
||||||
|
|
||||||
private RevisionNoteData parseJson(ChangeNoteUtil noteUtil, int offset)
|
protected void checkParsed() {
|
||||||
throws IOException{
|
checkState(raw != null, "revision note not parsed yet");
|
||||||
RevisionNoteData data;
|
|
||||||
try (InputStream is = new ByteArrayInputStream(
|
|
||||||
raw, offset, raw.length - offset);
|
|
||||||
Reader r = new InputStreamReader(is)) {
|
|
||||||
data = noteUtil.getGson().fromJson(r, RevisionNoteData.class);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,10 +37,12 @@ import java.util.Set;
|
|||||||
|
|
||||||
class RevisionNoteBuilder {
|
class RevisionNoteBuilder {
|
||||||
static class Cache {
|
static class Cache {
|
||||||
private final RevisionNoteMap revisionNoteMap;
|
private final RevisionNoteMap<?
|
||||||
|
extends RevisionNote<? extends Comment>> revisionNoteMap;
|
||||||
private final Map<RevId, RevisionNoteBuilder> builders;
|
private final Map<RevId, RevisionNoteBuilder> builders;
|
||||||
|
|
||||||
Cache(RevisionNoteMap revisionNoteMap) {
|
Cache(RevisionNoteMap<?
|
||||||
|
extends RevisionNote<? extends Comment>> revisionNoteMap) {
|
||||||
this.revisionNoteMap = revisionNoteMap;
|
this.revisionNoteMap = revisionNoteMap;
|
||||||
this.builders = new HashMap<>();
|
this.builders = new HashMap<>();
|
||||||
}
|
}
|
||||||
@@ -61,18 +63,20 @@ class RevisionNoteBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final byte[] baseRaw;
|
final byte[] baseRaw;
|
||||||
final List<Comment> baseComments;
|
final List<? extends Comment> baseComments;
|
||||||
final Map<Comment.Key, Comment> put;
|
final Map<Comment.Key, Comment> put;
|
||||||
final Set<Comment.Key> delete;
|
final Set<Comment.Key> delete;
|
||||||
|
|
||||||
private String pushCert;
|
private String pushCert;
|
||||||
|
|
||||||
RevisionNoteBuilder(RevisionNote base) {
|
RevisionNoteBuilder(RevisionNote<? extends Comment> base) {
|
||||||
if (base != null) {
|
if (base != null) {
|
||||||
baseRaw = base.raw;
|
baseRaw = base.getRaw();
|
||||||
baseComments = base.comments;
|
baseComments = base.getComments();
|
||||||
put = Maps.newHashMapWithExpectedSize(base.comments.size());
|
put = Maps.newHashMapWithExpectedSize(baseComments.size());
|
||||||
pushCert = base.pushCert;
|
if (base instanceof ChangeRevisionNote) {
|
||||||
|
pushCert = ((ChangeRevisionNote) base).getPushCert();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
baseRaw = new byte[0];
|
baseRaw = new byte[0];
|
||||||
baseComments = Collections.emptyList();
|
baseComments = Collections.emptyList();
|
||||||
@@ -82,9 +86,10 @@ class RevisionNoteBuilder {
|
|||||||
delete = new HashSet<>();
|
delete = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] build(ChangeNoteUtil noteUtil) throws IOException {
|
public byte[] build(ChangeNoteUtil noteUtil, boolean writeJson)
|
||||||
|
throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
if (noteUtil.getWriteJson()) {
|
if (writeJson) {
|
||||||
buildNoteJson(noteUtil, out);
|
buildNoteJson(noteUtil, out);
|
||||||
} else {
|
} else {
|
||||||
buildNoteLegacy(noteUtil, out);
|
buildNoteLegacy(noteUtil, out);
|
||||||
@@ -123,7 +128,7 @@ class RevisionNoteBuilder {
|
|||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildNoteJson(final ChangeNoteUtil noteUtil, OutputStream out)
|
private void buildNoteJson(ChangeNoteUtil noteUtil, OutputStream out)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Multimap<Integer, Comment> comments = buildCommentMap();
|
Multimap<Integer, Comment> comments = buildCommentMap();
|
||||||
if (comments.isEmpty() && pushCert == null) {
|
if (comments.isEmpty() && pushCert == null) {
|
||||||
|
@@ -16,6 +16,7 @@ package com.google.gerrit.server.notedb;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
|
||||||
@@ -28,30 +29,45 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
class RevisionNoteMap {
|
class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
|
||||||
final NoteMap noteMap;
|
final NoteMap noteMap;
|
||||||
final ImmutableMap<RevId, RevisionNote> revisionNotes;
|
final ImmutableMap<RevId, T> revisionNotes;
|
||||||
|
|
||||||
static RevisionNoteMap parse(ChangeNoteUtil noteUtil,
|
static RevisionNoteMap<ChangeRevisionNote> parse(ChangeNoteUtil noteUtil,
|
||||||
Change.Id changeId, ObjectReader reader, NoteMap noteMap,
|
Change.Id changeId, ObjectReader reader, NoteMap noteMap,
|
||||||
PatchLineComment.Status status)
|
PatchLineComment.Status status)
|
||||||
throws ConfigInvalidException, IOException {
|
throws ConfigInvalidException, IOException {
|
||||||
Map<RevId, RevisionNote> result = new HashMap<>();
|
Map<RevId, ChangeRevisionNote> result = new HashMap<>();
|
||||||
for (Note note : noteMap) {
|
for (Note note : noteMap) {
|
||||||
RevisionNote rn = new RevisionNote(
|
ChangeRevisionNote rn = new ChangeRevisionNote(
|
||||||
noteUtil, changeId, reader, note.getData(), status);
|
noteUtil, changeId, reader, note.getData(), status);
|
||||||
|
rn.parse();
|
||||||
result.put(new RevId(note.name()), rn);
|
result.put(new RevId(note.name()), rn);
|
||||||
}
|
}
|
||||||
return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
|
return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
static RevisionNoteMap emptyMap() {
|
static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
|
||||||
return new RevisionNoteMap(NoteMap.newEmptyMap(),
|
ChangeNoteUtil noteUtil, ObjectReader reader, NoteMap noteMap)
|
||||||
ImmutableMap.<RevId, RevisionNote> of());
|
throws ConfigInvalidException, IOException {
|
||||||
|
Map<RevId, RobotCommentsRevisionNote> result = new HashMap<>();
|
||||||
|
for (Note note : noteMap) {
|
||||||
|
RobotCommentsRevisionNote rn = new RobotCommentsRevisionNote(
|
||||||
|
noteUtil, reader, note.getData());
|
||||||
|
rn.parse();
|
||||||
|
result.put(new RevId(note.name()), rn);
|
||||||
|
}
|
||||||
|
return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends RevisionNote<? extends Comment>> RevisionNoteMap<T>
|
||||||
|
emptyMap() {
|
||||||
|
return new RevisionNoteMap<>(NoteMap.newEmptyMap(),
|
||||||
|
ImmutableMap.<RevId, T> of());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionNoteMap(NoteMap noteMap,
|
private RevisionNoteMap(NoteMap noteMap,
|
||||||
ImmutableMap<RevId, RevisionNote> revisionNotes) {
|
ImmutableMap<RevId, T> revisionNotes) {
|
||||||
this.noteMap = noteMap;
|
this.noteMap = noteMap;
|
||||||
this.revisionNotes = revisionNotes;
|
this.revisionNotes = revisionNotes;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (C) 2016 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.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
|
||||||
|
public interface Factory {
|
||||||
|
RobotCommentNotes create(Change change);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Change change;
|
||||||
|
|
||||||
|
private ImmutableListMultimap<RevId, RobotComment> comments;
|
||||||
|
private RevisionNoteMap<RobotCommentsRevisionNote> revisionNoteMap;
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
RobotCommentNotes(
|
||||||
|
Args args,
|
||||||
|
@Assisted Change change) {
|
||||||
|
super(args, change.getId(), false);
|
||||||
|
this.change = change;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevisionNoteMap<RobotCommentsRevisionNote> getRevisionNoteMap() {
|
||||||
|
return revisionNoteMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableListMultimap<RevId, RobotComment> getComments() {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsComment(RobotComment c) {
|
||||||
|
for (RobotComment existing : comments.values()) {
|
||||||
|
if (c.key.equals(existing.key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getRefName() {
|
||||||
|
return RefNames.robotCommentsRef(getChangeId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLoad(LoadHandle handle)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
ObjectId rev = handle.id();
|
||||||
|
if (rev == null) {
|
||||||
|
loadDefaults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevCommit tipCommit = handle.walk().parseCommit(rev);
|
||||||
|
ObjectReader reader = handle.walk().getObjectReader();
|
||||||
|
revisionNoteMap = RevisionNoteMap.parseRobotComments(args.noteUtil, reader,
|
||||||
|
NoteMap.read(reader, tipCommit));
|
||||||
|
Multimap<RevId, RobotComment> cs = ArrayListMultimap.create();
|
||||||
|
for (RobotCommentsRevisionNote rn :
|
||||||
|
revisionNoteMap.revisionNotes.values()) {
|
||||||
|
for (RobotComment c : rn.getComments()) {
|
||||||
|
cs.put(new RevId(c.revId), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comments = ImmutableListMultimap.copyOf(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadDefaults() {
|
||||||
|
comments = ImmutableListMultimap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Project.NameKey getProjectName() {
|
||||||
|
return change.getProject();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,222 @@
|
|||||||
|
// Copyright (C) 2016 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.MoreObjects.firstNonNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.gerrit.reviewdb.client.RefNames.robotCommentsRef;
|
||||||
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
|
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.PersonIdent;
|
||||||
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single delta to apply atomically to a change.
|
||||||
|
* <p>
|
||||||
|
* This delta contains only robot comments on a single patch set of a change by
|
||||||
|
* a single author. This delta will become a single commit in the repository.
|
||||||
|
* <p>
|
||||||
|
* This class is not thread safe.
|
||||||
|
*/
|
||||||
|
public class RobotCommentUpdate extends AbstractChangeUpdate{
|
||||||
|
public interface Factory {
|
||||||
|
RobotCommentUpdate create(ChangeNotes notes, Account.Id accountId,
|
||||||
|
PersonIdent authorIdent, Date when);
|
||||||
|
RobotCommentUpdate create(Change change, Account.Id accountId,
|
||||||
|
PersonIdent authorIdent, Date when);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RobotComment> put = new ArrayList<>();
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
private RobotCommentUpdate(
|
||||||
|
@GerritPersonIdent PersonIdent serverIdent,
|
||||||
|
@AnonymousCowardName String anonymousCowardName,
|
||||||
|
NotesMigration migration,
|
||||||
|
ChangeNoteUtil noteUtil,
|
||||||
|
@Assisted ChangeNotes notes,
|
||||||
|
@Assisted Account.Id accountId,
|
||||||
|
@Assisted PersonIdent authorIdent,
|
||||||
|
@Assisted Date when) {
|
||||||
|
super(migration, noteUtil, serverIdent, anonymousCowardName, notes, null,
|
||||||
|
accountId, authorIdent, when);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
private RobotCommentUpdate(
|
||||||
|
@GerritPersonIdent PersonIdent serverIdent,
|
||||||
|
@AnonymousCowardName String anonymousCowardName,
|
||||||
|
NotesMigration migration,
|
||||||
|
ChangeNoteUtil noteUtil,
|
||||||
|
@Assisted Change change,
|
||||||
|
@Assisted Account.Id accountId,
|
||||||
|
@Assisted PersonIdent authorIdent,
|
||||||
|
@Assisted Date when) {
|
||||||
|
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
|
||||||
|
accountId, authorIdent, when);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putComment(RobotComment c) {
|
||||||
|
verifyComment(c);
|
||||||
|
put.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyComment(RobotComment comment) {
|
||||||
|
checkArgument(comment.author.getId().equals(accountId),
|
||||||
|
"The author for the following comment does not match the author of"
|
||||||
|
+ " this RobotCommentUpdate (%s): %s", accountId, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
|
||||||
|
ObjectId curr, CommitBuilder cb)
|
||||||
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
|
RevisionNoteMap<RobotCommentsRevisionNote> rnm =
|
||||||
|
getRevisionNoteMap(rw, curr);
|
||||||
|
Set<RevId> updatedRevs =
|
||||||
|
Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
|
||||||
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
||||||
|
|
||||||
|
for (RobotComment c : put) {
|
||||||
|
cache.get(new RevId(c.revId)).putComment(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
||||||
|
boolean touchedAnyRevs = false;
|
||||||
|
boolean hasComments = false;
|
||||||
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
|
updatedRevs.add(e.getKey());
|
||||||
|
ObjectId id = ObjectId.fromString(e.getKey().get());
|
||||||
|
byte[] data = e.getValue().build(noteUtil, true);
|
||||||
|
if (!Arrays.equals(data, e.getValue().baseRaw)) {
|
||||||
|
touchedAnyRevs = true;
|
||||||
|
}
|
||||||
|
if (data.length == 0) {
|
||||||
|
rnm.noteMap.remove(id);
|
||||||
|
} else {
|
||||||
|
hasComments = true;
|
||||||
|
ObjectId dataBlob = ins.insert(OBJ_BLOB, data);
|
||||||
|
rnm.noteMap.set(id, dataBlob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't touch any notes, tell the caller this was a no-op update. We
|
||||||
|
// couldn't have done this in isEmpty() below because we hadn't read the old
|
||||||
|
// data yet.
|
||||||
|
if (!touchedAnyRevs) {
|
||||||
|
return NO_OP_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we touched every revision and there are no comments left, tell the
|
||||||
|
// caller to delete the entire ref.
|
||||||
|
boolean touchedAllRevs = updatedRevs.equals(rnm.revisionNotes.keySet());
|
||||||
|
if (touchedAllRevs && !hasComments) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.setTreeId(rnm.noteMap.writeTree(ins));
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RevisionNoteMap<RobotCommentsRevisionNote> getRevisionNoteMap(
|
||||||
|
RevWalk rw, ObjectId curr)
|
||||||
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
|
if (curr.equals(ObjectId.zeroId())) {
|
||||||
|
return RevisionNoteMap.emptyMap();
|
||||||
|
}
|
||||||
|
if (migration.readChanges()) {
|
||||||
|
// If reading from changes is enabled, then the old RobotCommentNotes
|
||||||
|
// already parsed the revision notes. We can reuse them as long as the ref
|
||||||
|
// hasn't advanced.
|
||||||
|
ChangeNotes changeNotes = getNotes();
|
||||||
|
if (changeNotes != null) {
|
||||||
|
RobotCommentNotes robotCommentNotes =
|
||||||
|
changeNotes.load().getRobotCommentNotes();
|
||||||
|
if (robotCommentNotes != null) {
|
||||||
|
ObjectId idFromNotes =
|
||||||
|
firstNonNull(robotCommentNotes.getRevision(), ObjectId.zeroId());
|
||||||
|
RevisionNoteMap<RobotCommentsRevisionNote> rnm =
|
||||||
|
robotCommentNotes.getRevisionNoteMap();
|
||||||
|
if (idFromNotes.equals(curr) && rnm != null) {
|
||||||
|
return rnm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoteMap noteMap;
|
||||||
|
if (!curr.equals(ObjectId.zeroId())) {
|
||||||
|
noteMap = NoteMap.read(rw.getObjectReader(), rw.parseCommit(curr));
|
||||||
|
} else {
|
||||||
|
noteMap = NoteMap.newEmptyMap();
|
||||||
|
}
|
||||||
|
// Even though reading from changes might not be enabled, we need to
|
||||||
|
// parse any existing revision notes so we can merge them.
|
||||||
|
return RevisionNoteMap.parseRobotComments(
|
||||||
|
noteUtil,
|
||||||
|
rw.getObjectReader(),
|
||||||
|
noteMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins,
|
||||||
|
ObjectId curr) throws OrmException, IOException {
|
||||||
|
CommitBuilder cb = new CommitBuilder();
|
||||||
|
cb.setMessage("Update robot comments");
|
||||||
|
try {
|
||||||
|
return storeCommentsInNotes(rw, ins, curr, cb);
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Project.NameKey getProjectName() {
|
||||||
|
return getNotes().getProjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getRefName() {
|
||||||
|
return robotCommentsRef(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return put.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.reviewdb.client.RobotComment;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RobotCommentsRevisionNote extends RevisionNote<RobotComment> {
|
||||||
|
private final ChangeNoteUtil noteUtil;
|
||||||
|
|
||||||
|
RobotCommentsRevisionNote(ChangeNoteUtil noteUtil, ObjectReader reader,
|
||||||
|
ObjectId noteId) {
|
||||||
|
super(reader, noteId);
|
||||||
|
this.noteUtil = noteUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<RobotComment> parse(byte[] raw, int offset)
|
||||||
|
throws IOException {
|
||||||
|
try (InputStream is = new ByteArrayInputStream(
|
||||||
|
raw, offset, raw.length - offset);
|
||||||
|
Reader r = new InputStreamReader(is)) {
|
||||||
|
return noteUtil.getGson().fromJson(r,
|
||||||
|
RobotCommentsRevisionNoteData.class).comments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (C) 2016 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.gerrit.reviewdb.client.RobotComment;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RobotCommentsRevisionNoteData {
|
||||||
|
List<RobotComment> comments;
|
||||||
|
}
|
Reference in New Issue
Block a user