Merge "Upgrade prolog-cafe to 1.4.2"
diff --git a/BUILD b/BUILD
index 7ae3589..76e2177 100644
--- a/BUILD
+++ b/BUILD
@@ -10,4 +10,4 @@
pkg_war(name = 'gerrit')
pkg_war(name = 'headless', ui = None)
-pkg_war(name = 'release', ui = 'ui_optdbg_r', context = ['//plugins:core'])
+#pkg_war(name = 'release', ui = 'ui_optdbg_r', context = ['//plugins:core'])
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 004d32b..1c7981a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2461,6 +2461,10 @@
+
A link:http://lucene.apache.org/[Lucene] index is used.
+
++
+* `ELASTICSEARCH`
++
+An link:http://www.elasticsearch.org/[Elasticsearch] index is used.
+
By default, `LUCENE`.
@@ -2585,6 +2589,43 @@
maxBufferedDocs = 500
----
+
+==== Elasticsearch configuration
+
+WARNING: ElasticSearch implementation is incomplete. Right now it is
+still using parts of Lucene index.
+
+Open and closed changes are indexed in a single index, separated
+into types 'open_changes' and 'closed_changes' respectively.
+
+The following settings are only used when the index type is
+`ELASTICSEARCH`.
+
+[[index.protocol]]index.protocol::
++
+Elasticsearch server protocol [http|https].
++
+Defaults to `http`.
+
+[[index.hostname]]index.hostname::
++
+Elasticsearch server hostname.
+
+Defaults to `localhost`.
+
+[[index.port]]index.port::
++
+Elasticsearch server port.
++
+Defauls to `9200`.
+
+[[index.name]]index.name::
++
+This setting can be used to index changes from multiple Gerrit
+instances in a single Elasticsearch cluster.
++
+Defaults to 'gerrit'.
+
[[ldap]]
=== Section ldap
diff --git a/Documentation/config-robot-comments.txt b/Documentation/config-robot-comments.txt
new file mode 100644
index 0000000..cf5de10
--- /dev/null
+++ b/Documentation/config-robot-comments.txt
@@ -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
+---------
diff --git a/Documentation/index.txt b/Documentation/index.txt
index f53463c..06a416d 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -45,6 +45,7 @@
. link:config-hooks.html[Hooks]
. link:config-mail.html[Mail Templates]
. link:config-cla.html[Contributor Agreements]
+. link:config-robot-comments.html[Robot Comments]
== Server Administration
. link:install.html[Installation Guide]
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 95fa1c9..88c1765 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3904,6 +3904,102 @@
}
----
+[[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
--
@@ -5465,6 +5561,9 @@
|`comments` |optional|
The comments that should be added as a map that maps a file path to a
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|
Whether all labels are required to be within the user's permitted ranges
based on access controls. +
@@ -5591,6 +5690,29 @@
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]]
=== RuleInput
The `RuleInput` entity contains information to test a Prolog rule.
diff --git a/ReleaseNotes/ReleaseNotes-2.13.2.txt b/ReleaseNotes/ReleaseNotes-2.13.2.txt
new file mode 100644
index 0000000..87e902c
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.13.2.txt
@@ -0,0 +1,34 @@
+= Release notes for Gerrit 2.13.2
+
+Gerrit 2.13.2 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.13.2.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.13.2.war]
+
+== Schema Upgrade
+
+There are no schema changes from link:ReleaseNotes-2.13.1.html[2.13.1].
+
+== Bug Fixes
+
+* link:https://bugs.chromium.org/p/gerrit/issues/detail?id=4630[Issue 4630]:
+Fix server error when navigating up to change while 'Working' is displayed.
+
+* link:https://bugs.chromium.org/p/gerrit/issues/detail?id=4631[Issue 4631]:
+Read project watches from database.
++
+Project watches were being read from the git backend by default, but the
+migration to git is not yet completed.
+
+* Hooks plugin: Fix incorrect value passed to `--change-url` parameter.
++
+The URL was being generated using the change's Change-Id rather than the
+change number.
+
+* Check for CLA when creating project config changes from the web UI.
++
+If contributor agreements were enabled and required for a project, and
+the user had not signed a CLA, it was still possible to upload changes
+for review on `refs/meta/config` by making changes in the project access
+editor and pressing 'Save for Review'.
+
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 2938c1c6..945f09f 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -2,6 +2,7 @@
[[s2_13]]
== Version 2.13.x
+* link:ReleaseNotes-2.13.2.html[2.13.2]
* link:ReleaseNotes-2.13.1.html[2.13.1]
* link:ReleaseNotes-2.13.html[2.13]
diff --git a/WORKSPACE b/WORKSPACE
index 05f1b22..581c936 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -318,6 +318,12 @@
)
maven_jar(
+ name = 'commons_lang3',
+ artifact = 'org.apache.commons:commons-lang3:3.3.2',
+ sha1 = '90a3822c38ec8c996e84c16a3477ef632cbc87a3',
+)
+
+maven_jar(
name = 'commons_dbcp',
artifact = 'commons-dbcp:commons-dbcp:1.4',
sha1 = '30be73c965cc990b153a100aaaaafcf239f82d39',
@@ -429,6 +435,13 @@
sha1 = 'f0bc3114a6b43f8e64a33c471d5b9e8ddc51564d',
)
+
+maven_jar(
+ name = 'lucene_codecs',
+ artifact = 'org.apache.lucene:lucene-codecs:' + LUCENE_VERS,
+ sha1 = 'e01fe463d9490bb1b4a6a168e771f7b7255a50b1',
+)
+
maven_jar(
name = 'backward_codecs',
artifact = 'org.apache.lucene:lucene-backward-codecs:' + LUCENE_VERS,
@@ -447,6 +460,55 @@
sha1 = '8ac921563e744463605284c6d9d2d95e1be5b87c',
)
+
+maven_jar(
+ name = 'lucene_highlighter',
+ artifact = 'org.apache.lucene:lucene-highlighter:' + LUCENE_VERS,
+ sha1 = 'd127ac514e9df965ab0b57d92bbe0c68d3d145b8',
+)
+
+maven_jar(
+ name = 'lucene_join',
+ artifact = 'org.apache.lucene:lucene-join:'+ LUCENE_VERS,
+ sha1 = 'dac1b322508f3f2696ecc49a97311d34d8382054',
+)
+
+maven_jar(
+ name = 'lucene_memory',
+ artifact = 'org.apache.lucene:lucene-memory:' + LUCENE_VERS,
+ sha1 = '7409db9863d8fbc265c27793c6cc7511304182c2',
+)
+
+maven_jar(
+ name = 'lucene_misc',
+ artifact = 'org.apache.lucene:lucene-misc:' + LUCENE_VERS,
+ sha1 = '37bbe5a2fb429499dfbe75d750d1778881fff45d',
+)
+
+maven_jar(
+ name = 'lucene_sandbox',
+ artifact = 'org.apache.lucene:lucene-sandbox:' + LUCENE_VERS,
+ sha1 = '30a91f120706ba66732d5a974b56c6971b3c8a16',
+)
+
+maven_jar(
+ name = 'lucene_spatial',
+ artifact = 'org.apache.lucene:lucene-spatial:' + LUCENE_VERS,
+ sha1 = '8ed7a9a43d78222038573dd1c295a61f3c0bb0db',
+)
+
+maven_jar(
+ name = 'lucene_suggest',
+ artifact = 'org.apache.lucene:lucene-suggest:' + LUCENE_VERS,
+ sha1 = 'e8316b37dddcf2092a54dab2ce6aad0d5ad78585',
+)
+
+maven_jar(
+ name = 'lucene_queries',
+ artifact = 'org.apache.lucene:lucene-queries:' + LUCENE_VERS,
+ sha1 = '692f1ad887cf4e006a23f45019e6de30f3312d3f',
+)
+
maven_jar(
name = 'mime_util',
artifact = 'eu.medsea.mimeutil:mime-util:2.1.3',
@@ -798,3 +860,98 @@
artifact = "org.jruby:jruby-complete:9.1.5.0",
sha1 = "00d0003e99da3c4d830b12c099691ce910c84e39",
)
+
+maven_jar(
+ name = 'elasticsearch',
+ artifact = 'org.elasticsearch:elasticsearch:2.4.0',
+ sha1 = 'aeb9704a76fa8654c348f38fcbb993a952a7ab07',
+)
+
+# Java REST client for Elasticsearch.
+ELASTIC_VERSION = '0.1.7'
+
+maven_jar(
+ name = 'jest_common',
+ artifact = 'io.searchbox:jest-common:' + ELASTIC_VERSION,
+ sha1 = 'ff6e2694405557a3a02b444cb7f7da28c4d99f07',
+)
+
+maven_jar(
+ name = 'jest',
+ artifact = 'io.searchbox:jest:' + ELASTIC_VERSION,
+ sha1 = '686619c7141edb50b562ad2a39d32ea4cf20b567',
+)
+
+maven_jar(
+ name = 'compress_lzf',
+ artifact = 'com.ning:compress-lzf:1.0.2',
+ sha1 = '62896e6fca184c79cc01a14d143f3ae2b4f4b4ae',
+)
+
+maven_jar(
+ name = 'hppc',
+ artifact = 'com.carrotsearch:hppc:0.7.1',
+ sha1 = '8b5057f74ea378c0150a1860874a3ebdcb713767',
+)
+
+maven_jar(
+ name = 'jsr166e',
+ artifact = 'com.twitter:jsr166e:1.1.0',
+ sha1 = '233098147123ee5ddcd39ffc57ff648be4b7e5b2',
+)
+
+maven_jar(
+ name = 'netty',
+ artifact = 'io.netty:netty:3.10.0.Final',
+ sha1 = 'ad61cd1bba067e6634ddd3e160edf0727391ac30',
+)
+
+maven_jar(
+ name = 't_digest',
+ artifact = 'com.tdunning:t-digest:3.0',
+ sha1 = '84ccf145ac2215e6bfa63baa3101c0af41017cfc',
+)
+
+maven_jar(
+ name = 'jna',
+ artifact = 'net.java.dev.jna:jna:4.1.0',
+ sha1 = '1c12d070e602efd8021891cdd7fd18bc129372d4',
+)
+
+JACKSON_VERSION = '2.6.6'
+
+maven_jar(
+ name = 'jackson_core',
+ artifact = 'com.fasterxml.jackson.core:jackson-core:' + JACKSON_VERSION,
+ sha1 = '02eb801df67aacaf5b1deb4ac626e1964508e47b',
+)
+
+maven_jar(
+ name = 'jackson_dataformat_smile',
+ artifact = 'com.fasterxml.jackson.dataformat:jackson-dataformat-smile:' + JACKSON_VERSION,
+ sha1 = 'ccbfc948748ed2754a58c1af9e0a02b5cc1aed69',
+)
+
+maven_jar(
+ name = 'jackson_dataformat_cbor',
+ artifact = 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:' + JACKSON_VERSION,
+ sha1 = '34c7b7ff495fc6b049612bdc9db0900a68e112f8',
+)
+
+maven_jar(
+ name = 'httpasyncclient',
+ artifact = 'org.apache.httpcomponents:httpasyncclient:4.1.2',
+ sha1 = '95aa3e6fb520191a0970a73cf09f62948ee614be',
+)
+
+maven_jar(
+ name = 'httpcore_nio',
+ artifact = 'org.apache.httpcomponents:httpcore-nio:' + HTTPCOMP_VERS,
+ sha1 = 'a8c5e3c3bfea5ce23fb647c335897e415eb442e3',
+)
+
+maven_jar(
+ name = 'httpcore_niossl',
+ artifact = 'org.apache.httpcomponents:httpcore-niossl:4.0-alpha6',
+ sha1 = '9c662e7247ca8ceb1de5de629f685c9ef3e4ab58',
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
new file mode 100644
index 0000000..064b206
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -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();
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/BUCK b/gerrit-elasticsearch/BUCK
new file mode 100644
index 0000000..a2641df
--- /dev/null
+++ b/gerrit-elasticsearch/BUCK
@@ -0,0 +1,51 @@
+java_library(
+ name = 'elasticsearch',
+ srcs = glob(['src/main/java/**/*.java']),
+ deps = [
+ '//gerrit-antlr:query_exception',
+ '//gerrit-extension-api:api',
+ '//gerrit-lucene:lucene', # only for LuceneAccountIndex
+ '//gerrit-reviewdb:client',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//gerrit-index:index',
+ '//lib:gson',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib:protobuf',
+ '//lib/commons:codec',
+ '//lib/commons:lang',
+ '//lib/elasticsearch:elasticsearch',
+ '//lib/elasticsearch:jest',
+ '//lib/elasticsearch:jest-common',
+ '//lib/guice:guice',
+ '//lib/guice:guice-assistedinject',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib/joda:joda-time',
+ '//lib/log:api',
+ '//lib/lucene:lucene-analyzers-common',
+ '//lib/lucene:lucene-core',
+ ],
+ visibility = ['PUBLIC'],
+)
+
+java_test(
+ name = 'elasticsearch_tests',
+ labels = ['elastic'],
+ srcs = glob(['src/test/java/**/*.java']),
+ deps = [
+ ':elasticsearch',
+ '//gerrit-extension-api:api',
+ '//gerrit-server:server',
+ '//gerrit-server:testutil',
+ '//gerrit-server:query_tests',
+ '//lib:gson',
+ '//lib:guava',
+ '//lib:junit',
+ '//lib:truth',
+ '//lib/elasticsearch:elasticsearch',
+ '//lib/guice:guice',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib/jgit/org.eclipse.jgit.junit:junit',
+ ],
+)
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
new file mode 100644
index 0000000..8ea9f9a
--- /dev/null
+++ b/gerrit-elasticsearch/BUILD
@@ -0,0 +1,53 @@
+java_library(
+ name = 'elasticsearch',
+ srcs = glob(['src/main/java/**/*.java']),
+ deps = [
+ '//gerrit-antlr:query_exception',
+ '//gerrit-extension-api:api',
+ '//gerrit-lucene:lucene', # only for LuceneAccountIndex
+ '//gerrit-reviewdb:client',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//gerrit-index:index',
+ '//lib:gson',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib:protobuf',
+ '//lib/commons:codec',
+ '//lib/commons:lang',
+ '//lib/elasticsearch:elasticsearch',
+ '//lib/elasticsearch:jest',
+ '//lib/elasticsearch:jest-common',
+ '//lib/guice:guice',
+ '//lib/guice:guice-assistedinject',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib/joda:joda-time',
+ '//lib/log:api',
+ '//lib/lucene:lucene-analyzers-common',
+ '//lib/lucene:lucene-core',
+ ],
+ visibility = ['//visibility:public'],
+)
+
+load('//tools/bzl:junit.bzl', 'junit_tests')
+
+junit_tests(
+ name = 'elasticsearch_tests',
+ tags = ['elastic'],
+ srcs = glob(['src/test/java/**/*.java']),
+ deps = [
+ ':elasticsearch',
+ '//gerrit-extension-api:api',
+ '//gerrit-server:server',
+ '//gerrit-server:testutil',
+ '//gerrit-server:query_tests_code',
+ '//lib:gson',
+ '//lib:guava',
+ '//lib:junit',
+ '//lib:truth',
+ '//lib/elasticsearch:elasticsearch',
+ '//lib/guice:guice',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib/jgit/org.eclipse.jgit.junit:junit',
+ ],
+)
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
new file mode 100644
index 0000000..a46edc7
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -0,0 +1,207 @@
+// Copyright (C) 2014 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.elasticsearch;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.index.IndexUtils;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.Index;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.Schema.Values;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.Config;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
+
+import io.searchbox.client.JestClientFactory;
+import io.searchbox.client.JestResult;
+import io.searchbox.client.config.HttpClientConfig;
+import io.searchbox.client.http.JestHttpClient;
+import io.searchbox.core.Bulk;
+import io.searchbox.core.Delete;
+import io.searchbox.indices.CreateIndex;
+import io.searchbox.indices.DeleteIndex;
+import io.searchbox.indices.IndicesExists;
+
+abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
+ private static final String DEFAULT_INDEX_NAME = "gerrit";
+
+ private final Schema<V> schema;
+ private final FillArgs fillArgs;
+ private final SitePaths sitePaths;
+
+ protected final boolean refresh;
+ protected final String indexName;
+ protected final JestHttpClient client;
+
+
+ @Inject
+ AbstractElasticIndex(@GerritServerConfig Config cfg,
+ FillArgs fillArgs,
+ SitePaths sitePaths,
+ @Assisted Schema<V> schema) {
+ this.fillArgs = fillArgs;
+ this.sitePaths = sitePaths;
+ this.schema = schema;
+ String protocol = getRequiredConfigOption(cfg, "protocol");
+ String hostname = getRequiredConfigOption(cfg, "hostname");
+ String port = getRequiredConfigOption(cfg, "port");
+
+ this.indexName =
+ firstNonNull(cfg.getString("index", null, "name"), DEFAULT_INDEX_NAME);
+
+ // By default Elasticsearch has a 1s delay before changes are available in
+ // the index. Setting refresh(true) on calls to the index makes the index
+ // refresh immediately.
+ //
+ // Discovery should be disabled during test mode to prevent spurious
+ // connection failures caused by the client starting up and being ready
+ // before the test node.
+ //
+ // This setting should only be set to true during testing, and is not
+ // documented.
+ this.refresh = cfg.getBoolean("index", "elasticsearch", "test", false);
+
+ String url = buildUrl(protocol, hostname, port);
+ JestClientFactory factory = new JestClientFactory();
+ factory.setHttpClientConfig(new HttpClientConfig
+ .Builder(url)
+ .multiThreaded(true)
+ .discoveryEnabled(!refresh)
+ .discoveryFrequency(1L, TimeUnit.MINUTES)
+ .build());
+ client = (JestHttpClient) factory.getObject();
+ }
+
+ @Override
+ public Schema<V> getSchema() {
+ return schema;
+ }
+
+ @Override
+ public void close() {
+ client.shutdownClient();
+ }
+
+ @Override
+ public void markReady(boolean ready) throws IOException {
+ IndexUtils.setReady(sitePaths, indexName, schema.getVersion(), ready);
+ }
+
+ @Override
+ public void delete(K c) throws IOException {
+ Bulk bulk = addActions(new Bulk.Builder(), c).refresh(refresh).build();
+ JestResult result = client.execute(bulk);
+ if (!result.isSucceeded()) {
+ throw new IOException(String.format(
+ "Failed to delete change %s in index %s: %s", c, indexName,
+ result.getErrorMessage()));
+ }
+ }
+
+ @Override
+ public void deleteAll() throws IOException {
+ // Delete the index, if it exists.
+ JestResult result = client.execute(
+ new IndicesExists.Builder(indexName).build());
+ if (result.isSucceeded()) {
+ result = client.execute(
+ new DeleteIndex.Builder(indexName).build());
+ if (!result.isSucceeded()) {
+ throw new IOException(String.format(
+ "Failed to delete index %s: %s", indexName,
+ result.getErrorMessage()));
+ }
+ }
+
+ // Recreate the index.
+ result = client.execute(
+ new CreateIndex.Builder(indexName).settings(getMappings()).build());
+ if (!result.isSucceeded()) {
+ String error = String.format("Failed to create index %s: %s",
+ indexName, result.getErrorMessage());
+ throw new IOException(error);
+ }
+ }
+
+ protected abstract Bulk.Builder addActions(Bulk.Builder builder, K c);
+
+ protected abstract String getMappings();
+
+ protected abstract String getId(V v);
+
+ protected Delete delete(String type, K c) {
+ String id = c.toString();
+ return new Delete.Builder(id)
+ .index(indexName)
+ .type(type)
+ .build();
+ }
+
+ protected io.searchbox.core.Index insert(String type, V v) throws IOException {
+ String id = getId(v);
+ String doc = toDoc(v);
+ return new io.searchbox.core.Index.Builder(doc)
+ .index(indexName)
+ .type(type)
+ .id(id)
+ .build();
+ }
+
+ private String toDoc(V v) throws IOException {
+ XContentBuilder builder = jsonBuilder().startObject();
+ for (Values<V> values : schema.buildFields(v, fillArgs)) {
+ String name = values.getField().getName();
+ if (values.getField().isRepeatable()) {
+ builder.array(name, values.getValues());
+ } else {
+ Object element = Iterables.getOnlyElement(values.getValues(), "");
+ if (!(element instanceof String) || !((String) element).isEmpty()) {
+ builder.field(name, element);
+ }
+ }
+ }
+ return builder.endObject().string();
+ }
+
+ private String getRequiredConfigOption(Config cfg, String name) {
+ String option = cfg.getString("index", null, name);
+ checkState(!Strings.isNullOrEmpty(option), "index." + name + " must be supplied");
+ return option;
+ }
+
+ private String buildUrl(String protocol, String hostname, String port) {
+ try {
+ return new URL(protocol, hostname, Integer.parseInt(port), "").toString();
+ } catch (MalformedURLException | NumberFormatException e) {
+ throw new RuntimeException(
+ "Cannot build url to Elasticsearch from values: protocol=" + protocol
+ + " hostname=" + hostname + " port=" + port, e);
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
new file mode 100644
index 0000000..c55ea1c
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -0,0 +1,389 @@
+// Copyright (C) 2014 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.elasticsearch;
+
+import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
+import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
+import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.IndexUtils;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.QueryOptions;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.index.change.ChangeField.ChangeProtoField;
+import com.google.gerrit.server.index.change.ChangeField.PatchSetApprovalProtoField;
+import com.google.gerrit.server.index.change.ChangeField.PatchSetProtoField;
+import com.google.gerrit.server.index.change.ChangeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexRewriter;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.apache.commons.codec.binary.Base64;
+import org.eclipse.jgit.lib.Config;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import io.searchbox.client.JestResult;
+import io.searchbox.core.Bulk;
+import io.searchbox.core.Bulk.Builder;
+import io.searchbox.core.Search;
+import io.searchbox.core.search.sort.Sort;
+import io.searchbox.core.search.sort.Sort.Sorting;
+
+/** Secondary index implementation using Elasticsearch. */
+class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
+ implements ChangeIndex {
+ private static final Logger log =
+ LoggerFactory.getLogger(ElasticChangeIndex.class);
+
+ static class ChangeMapping {
+ MappingProperties openChanges;
+ MappingProperties closedChanges;
+
+ ChangeMapping(Schema<ChangeData> schema) {
+ ElasticMapping.Builder mappingBuilder = new ElasticMapping.Builder();
+ for (FieldDef<?, ?> field : schema.getFields().values()) {
+ String name = field.getName();
+ FieldType<?> fieldType = field.getType();
+ if (fieldType == FieldType.EXACT) {
+ mappingBuilder.addExactField(name);
+ } else if (fieldType == FieldType.TIMESTAMP) {
+ mappingBuilder.addTimestamp(name);
+ } else if (fieldType == FieldType.INTEGER
+ || fieldType == FieldType.INTEGER_RANGE
+ || fieldType == FieldType.LONG) {
+ mappingBuilder.addNumber(name);
+ } else if (fieldType == FieldType.PREFIX
+ || fieldType == FieldType.FULL_TEXT
+ || fieldType == FieldType.STORED_ONLY) {
+ mappingBuilder.addString(name);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported filed type " + fieldType.getName());
+ }
+ }
+ MappingProperties mapping = mappingBuilder.build();
+ openChanges = mapping;
+ closedChanges = mapping;
+ }
+ }
+
+ static final String OPEN_CHANGES = "open_changes";
+ static final String CLOSED_CHANGES = "closed_changes";
+
+ private final Gson gson;
+ private final ChangeMapping mapping;
+ private final Provider<ReviewDb> db;
+ private final ElasticQueryBuilder queryBuilder;
+ private final ChangeData.Factory changeDataFactory;
+
+ @AssistedInject
+ ElasticChangeIndex(
+ @GerritServerConfig Config cfg,
+ Provider<ReviewDb> db,
+ ChangeData.Factory changeDataFactory,
+ FillArgs fillArgs,
+ SitePaths sitePaths,
+ @Assisted Schema<ChangeData> schema) {
+ super(cfg, fillArgs, sitePaths, schema);
+ this.db = db;
+ this.changeDataFactory = changeDataFactory;
+ mapping = new ChangeMapping(schema);
+
+ this.queryBuilder = new ElasticQueryBuilder();
+ this.gson = new GsonBuilder()
+ .setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
+ }
+
+ private static <T> List<T> decodeProtos(JsonObject doc, String fieldName,
+ ProtobufCodec<T> codec) {
+ return FluentIterable.from(doc.getAsJsonArray(fieldName))
+ .transform(i -> codec.decode(Base64.decodeBase64(i.toString())))
+ .toList();
+ }
+
+ @Override
+ public void replace(ChangeData cd) throws IOException {
+ String deleteIndex;
+ String insertIndex;
+
+ try {
+ if (cd.change().getStatus().isOpen()) {
+ insertIndex = OPEN_CHANGES;
+ deleteIndex = CLOSED_CHANGES;
+ } else {
+ insertIndex = CLOSED_CHANGES;
+ deleteIndex = OPEN_CHANGES;
+ }
+ } catch (OrmException e) {
+ throw new IOException(e);
+ }
+
+ Bulk bulk = new Bulk.Builder()
+ .defaultIndex(indexName)
+ .defaultType("changes")
+ .addAction(insert(insertIndex, cd))
+ .addAction(delete(deleteIndex, cd.getId()))
+ .refresh(refresh)
+ .build();
+ JestResult result = client.execute(bulk);
+ if (!result.isSucceeded()) {
+ throw new IOException(String.format(
+ "Failed to replace change %s in index %s: %s", cd.getId(), indexName,
+ result.getErrorMessage()));
+ }
+ }
+
+ @Override
+ public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts)
+ throws QueryParseException {
+ Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
+ List<String> indexes = Lists.newArrayListWithCapacity(2);
+ if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
+ indexes.add(OPEN_CHANGES);
+ }
+ if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+ indexes.add(CLOSED_CHANGES);
+ }
+ return new QuerySource(indexes, p, opts);
+ }
+
+ @Override
+ protected Builder addActions(Builder builder, Id c) {
+ return builder
+ .addAction(delete(OPEN_CHANGES, c))
+ .addAction(delete(OPEN_CHANGES, c));
+ }
+
+ @Override
+ protected String getMappings() {
+ return gson.toJson(ImmutableMap.of("mappings", mapping));
+ }
+
+ @Override
+ protected String getId(ChangeData cd) {
+ return cd.getId().toString();
+ }
+
+ private class QuerySource implements ChangeDataSource {
+ private final Search search;
+ private final Set<String> fields;
+
+ public QuerySource(List<String> types, Predicate<ChangeData> p,
+ QueryOptions opts) throws QueryParseException {
+ List<Sort> sorts = ImmutableList.of(
+ new Sort(ChangeField.UPDATED.getName(), Sorting.DESC),
+ new Sort(ChangeField.LEGACY_ID.getName(), Sorting.DESC));
+ for (Sort sort : sorts) {
+ sort.setIgnoreUnmapped();
+ }
+ QueryBuilder qb = queryBuilder.toQueryBuilder(p);
+ fields = IndexUtils.fields(opts);
+ SearchSourceBuilder searchSource = new SearchSourceBuilder()
+ .query(qb)
+ .from(opts.start())
+ .size(opts.limit())
+ .fields(Lists.newArrayList(fields));
+
+ search = new Search.Builder(searchSource.toString())
+ .addType(types)
+ .addSort(sorts)
+ .addIndex(indexName)
+ .build();
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ try {
+ List<ChangeData> results = Collections.emptyList();
+ JestResult result = client.execute(search);
+ if (result.isSucceeded()) {
+ JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
+ if (obj.get("hits") != null) {
+ JsonArray json = obj.getAsJsonArray("hits");
+ results = Lists.newArrayListWithCapacity(json.size());
+ for (int i = 0; i < json.size(); i++) {
+ results.add(toChangeData(json.get(i)));
+ }
+ }
+ } else {
+ log.error(result.getErrorMessage());
+ }
+ final List<ChangeData> r = Collections.unmodifiableList(results);
+ return new ResultSet<ChangeData>() {
+ @Override
+ public Iterator<ChangeData> iterator() {
+ return r.iterator();
+ }
+
+ @Override
+ public List<ChangeData> toList() {
+ return r;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ };
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ @Override
+ public boolean hasChange() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return search.toString();
+ }
+
+ private ChangeData toChangeData(JsonElement json) {
+ JsonElement sourceElement = json.getAsJsonObject().get("_source");
+ if (sourceElement == null) {
+ sourceElement = json.getAsJsonObject().get("fields");
+ }
+ JsonObject source = sourceElement.getAsJsonObject();
+ JsonElement c = source.get(ChangeField.CHANGE.getName());
+
+ if (c == null) {
+ int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
+ String projectName =
+ source.get(ChangeField.PROJECT.getName()).getAsString();
+ if (projectName == null) {
+ return changeDataFactory.createOnlyWhenNoteDbDisabled(
+ db.get(), new Change.Id(id));
+ }
+ return changeDataFactory.create(
+ db.get(), new Project.NameKey(projectName), new Change.Id(id));
+ }
+
+ ChangeData cd = changeDataFactory.create(db.get(),
+ ChangeProtoField.CODEC.decode(Base64.decodeBase64(c.getAsString())));
+
+ // Patch sets.
+ cd.setPatchSets(decodeProtos(
+ source, ChangeField.PATCH_SET.getName(), PatchSetProtoField.CODEC));
+
+ // Approvals.
+ if (source.get(ChangeField.APPROVAL.getName()) != null) {
+ cd.setCurrentApprovals(decodeProtos(source,
+ ChangeField.APPROVAL.getName(), PatchSetApprovalProtoField.CODEC));
+ } else if (fields.contains(ChangeField.APPROVAL.getName())) {
+ cd.setCurrentApprovals(Collections.emptyList());
+ }
+
+ JsonElement addedElement = source.get(ChangeField.ADDED.getName());
+ JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
+ if (addedElement != null && deletedElement != null) {
+ // Changed lines.
+ int added = addedElement.getAsInt();
+ int deleted = deletedElement.getAsInt();
+ if (added != 0 && deleted != 0) {
+ cd.setChangedLines(added, deleted);
+ }
+ }
+
+ // Mergeable.
+ JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
+ if (mergeableElement != null) {
+ String mergeable = mergeableElement.getAsString();
+ if ("1".equals(mergeable)) {
+ cd.setMergeable(true);
+ } else if ("0".equals(mergeable)) {
+ cd.setMergeable(false);
+ }
+ }
+
+ // Reviewed-by.
+ if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
+ JsonArray reviewedBy =
+ source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
+ if (reviewedBy.size() > 0) {
+ Set<Account.Id> accounts =
+ Sets.newHashSetWithExpectedSize(reviewedBy.size());
+ for (int i = 0; i < reviewedBy.size() ; i++) {
+ int aId = reviewedBy.get(i).getAsInt();
+ if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
+ break;
+ }
+ accounts.add(new Account.Id(aId));
+ }
+ cd.setReviewedBy(accounts);
+ }
+ } else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
+ cd.setReviewedBy(Collections.emptySet());
+ }
+
+ if (source.get(ChangeField.REVIEWER.getName()) != null) {
+ cd.setReviewers(
+ ChangeField.parseReviewerFieldValues(FluentIterable
+ .from(
+ source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
+ .transform(JsonElement::getAsString)));
+ } else if (fields.contains(ChangeField.REVIEWER.getName())) {
+ cd.setReviewers(ReviewerSet.empty());
+ }
+
+ return cd;
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
new file mode 100644
index 0000000..e108dca
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2014 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.elasticsearch;
+
+import com.google.gerrit.index.SingleVersionModule;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.lucene.LuceneAccountIndex;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.index.IndexConfig;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gerrit.server.index.change.ChangeIndex;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Map;
+
+public class ElasticIndexModule extends LifecycleModule {
+ private final int threads;
+ private final Map<String, Integer> singleVersions;
+
+ public static ElasticIndexModule singleVersionWithExplicitVersions(
+ Map<String, Integer> versions, int threads) {
+ return new ElasticIndexModule(versions, threads);
+ }
+
+ public static ElasticIndexModule latestVersionWithOnlineUpgrade() {
+ return new ElasticIndexModule(null, 0);
+ }
+
+ private ElasticIndexModule(Map<String, Integer> singleVersions, int threads) {
+ this.singleVersions = singleVersions;
+ this.threads = threads;
+ }
+
+ @Override
+ protected void configure() {
+ install(
+ new FactoryModuleBuilder()
+ .implement(ChangeIndex.class, ElasticChangeIndex.class)
+ .build(ChangeIndex.Factory.class));
+ install(
+ new FactoryModuleBuilder()
+ // until we implement Elasticsearch index for accounts we need to
+ // use Lucene to make all tests green and Gerrit server to work
+ .implement(AccountIndex.class, LuceneAccountIndex.class)
+ .build(AccountIndex.Factory.class));
+
+ install(new IndexModule(threads));
+ install(new SingleVersionModule(singleVersions));
+ }
+
+ @Provides
+ @Singleton
+ IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
+ return IndexConfig.fromConfig(cfg);
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
new file mode 100644
index 0000000..e3f7e96
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
@@ -0,0 +1,79 @@
+// 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.elasticsearch;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+class ElasticMapping {
+ static class Builder {
+ private final ImmutableMap.Builder<String, FieldProperties> fields =
+ new ImmutableMap.Builder<>();
+
+ MappingProperties build() {
+ MappingProperties properties = new MappingProperties();
+ properties.properties = fields.build();
+ return properties;
+ }
+
+ Builder addExactField(String name) {
+ FieldProperties key = new FieldProperties("string");
+ key.index = "not_analyzed";
+ FieldProperties properties = new FieldProperties("string");
+ properties.fields = ImmutableMap.of("key", key);
+ fields.put(name, properties);
+ return this;
+ }
+
+ Builder addTimestamp(String name) {
+ FieldProperties properties = new FieldProperties("date");
+ properties.type = "date";
+ properties.format = "dateOptionalTime";
+ fields.put(name, properties);
+ return this;
+ }
+
+ Builder addNumber(String name) {
+ fields.put(name, new FieldProperties("long"));
+ return this;
+ }
+
+ Builder addString(String name) {
+ fields.put(name, new FieldProperties("string"));
+ return this;
+ }
+
+ Builder add(String name, String type) {
+ fields.put(name, new FieldProperties(type));
+ return this;
+ }
+ }
+
+ static class MappingProperties {
+ Map<String, FieldProperties> properties;
+ }
+
+ static class FieldProperties {
+ String type;
+ String index;
+ String format;
+ Map<String, FieldProperties> fields;
+
+ FieldProperties(String type) {
+ this.type = type;
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
new file mode 100644
index 0000000..51b14a4
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
@@ -0,0 +1,181 @@
+// Copyright (C) 2014 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.elasticsearch;
+
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.IntegerRangePredicate;
+import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.index.TimestampRangePredicate;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.AfterPredicate;
+
+import org.apache.lucene.search.BooleanQuery;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.joda.time.DateTime;
+
+public class ElasticQueryBuilder {
+
+ protected <T> QueryBuilder toQueryBuilder(Predicate<T> p)
+ throws QueryParseException {
+ if (p instanceof AndPredicate) {
+ return and(p);
+ } else if (p instanceof OrPredicate) {
+ return or(p);
+ } else if (p instanceof NotPredicate) {
+ return not(p);
+ } else if (p instanceof IndexPredicate) {
+ return fieldQuery((IndexPredicate<T>) p);
+ } else {
+ throw new QueryParseException("cannot create query for index: " + p);
+ }
+ }
+
+ private <T> BoolQueryBuilder and(Predicate<T> p)
+ throws QueryParseException {
+ try {
+ BoolQueryBuilder b = QueryBuilders.boolQuery();
+ for (Predicate<T> c : p.getChildren()) {
+ b.must(toQueryBuilder(c));
+ }
+ return b;
+ } catch (BooleanQuery.TooManyClauses e) {
+ throw new QueryParseException("cannot create query for index: " + p, e);
+ }
+ }
+
+ private <T> BoolQueryBuilder or(Predicate<T> p)
+ throws QueryParseException {
+ try {
+ BoolQueryBuilder q = QueryBuilders.boolQuery();
+ for (Predicate<T> c : p.getChildren()) {
+ q.should(toQueryBuilder(c));
+ }
+ return q;
+ } catch (BooleanQuery.TooManyClauses e) {
+ throw new QueryParseException("cannot create query for index: " + p, e);
+ }
+ }
+
+ private <T> QueryBuilder not(Predicate<T> p)
+ throws QueryParseException {
+ Predicate<T> n = p.getChild(0);
+ if (n instanceof TimestampRangePredicate) {
+ return notTimestamp((TimestampRangePredicate<T>) n);
+ }
+
+ // Lucene does not support negation, start with all and subtract.
+ BoolQueryBuilder q = QueryBuilders.boolQuery();
+ q.must(QueryBuilders.matchAllQuery());
+ q.mustNot(toQueryBuilder(n));
+ return q;
+ }
+
+ private <T> QueryBuilder fieldQuery(IndexPredicate<T> p)
+ throws QueryParseException {
+ FieldType<?> type = p.getType();
+ FieldDef<?,?> field = p.getField();
+ String name = field.getName();
+ String value = p.getValue();
+
+ if (type == FieldType.INTEGER) {
+ // QueryBuilder encodes integer fields as prefix coded bits,
+ // which elasticsearch's queryString can't handle.
+ // Create integer terms with string representations instead.
+ return QueryBuilders.termQuery(name, value);
+ } else if (type == FieldType.INTEGER_RANGE) {
+ return intRangeQuery(p);
+ } else if (type == FieldType.TIMESTAMP) {
+ return timestampQuery(p);
+ } else if (type == FieldType.EXACT) {
+ return exactQuery(p);
+ } else if (type == FieldType.PREFIX) {
+ return QueryBuilders.matchPhrasePrefixQuery(name, value);
+ } else if (type == FieldType.FULL_TEXT) {
+ return QueryBuilders.matchPhraseQuery(name, value);
+ } else {
+ throw FieldType.badFieldType(p.getType());
+ }
+ }
+
+ private <T> QueryBuilder intRangeQuery(IndexPredicate<T> p)
+ throws QueryParseException {
+ if (p instanceof IntegerRangePredicate) {
+ IntegerRangePredicate<T> r = (IntegerRangePredicate<T>) p;
+ int minimum = r.getMinimumValue();
+ int maximum = r.getMaximumValue();
+ if (minimum == maximum) {
+ // Just fall back to a standard integer query.
+ return QueryBuilders.termQuery(p.getField().getName(), minimum);
+ }
+ return QueryBuilders.rangeQuery(p.getField().getName())
+ .gte(minimum)
+ .lte(maximum);
+ }
+ throw new QueryParseException("not an integer range: " + p);
+ }
+
+ private <T> QueryBuilder notTimestamp(TimestampRangePredicate<T> r)
+ throws QueryParseException {
+ if (r.getMinTimestamp().getTime() == 0) {
+ return QueryBuilders.rangeQuery(r.getField().getName())
+ .gt(new DateTime(r.getMaxTimestamp().getTime()));
+ }
+ throw new QueryParseException("cannot negate: " + r);
+ }
+
+ private <T> QueryBuilder timestampQuery(IndexPredicate<T> p)
+ throws QueryParseException {
+ if (p instanceof TimestampRangePredicate) {
+ TimestampRangePredicate<T> r =
+ (TimestampRangePredicate<T>) p;
+ if (p instanceof AfterPredicate) {
+ return QueryBuilders.rangeQuery(r.getField().getName())
+ .gte(new DateTime(r.getMinTimestamp().getTime()));
+ }
+ return QueryBuilders.rangeQuery(r.getField().getName())
+ .gte(new DateTime(r.getMinTimestamp().getTime()))
+ .lte(new DateTime(r.getMaxTimestamp().getTime()));
+ }
+ throw new QueryParseException("not a timestamp: " + p);
+ }
+
+ private <T> QueryBuilder exactQuery(IndexPredicate<T> p){
+ String name = p.getField().getName();
+ String value = p.getValue();
+
+ if (value.isEmpty()) {
+ return new BoolQueryBuilder().mustNot(QueryBuilders.existsQuery(name));
+ } else if (p instanceof RegexPredicate) {
+ if (value.startsWith("^")) {
+ value = value.substring(1);
+ }
+ if (value.endsWith("$") && !value.endsWith("\\$")
+ && !value.endsWith("\\\\$")) {
+ value = value.substring(0, value.length() - 1);
+ }
+ return QueryBuilders.regexpQuery(name + ".key", value);
+ } else {
+ return QueryBuilders.termQuery(name + ".key", value);
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
new file mode 100644
index 0000000..e2e7585
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
@@ -0,0 +1,178 @@
+// Copyright (C) 2014 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.elasticsearch;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.elasticsearch.ElasticChangeIndex.CLOSED_CHANGES;
+import static com.google.gerrit.elasticsearch.ElasticChangeIndex.OPEN_CHANGES;
+
+import com.google.common.base.Strings;
+import com.google.common.io.Files;
+import com.google.gerrit.elasticsearch.ElasticChangeIndex.ChangeMapping;
+import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
+import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.lib.Config;
+import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.Node;
+import org.elasticsearch.node.NodeBuilder;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+public class ElasticQueryChangesTest extends AbstractQueryChangesTest {
+ private static final Gson gson = new GsonBuilder()
+ .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+ private static Node node;
+ private static String port;
+ private static File elasticDir;
+
+ static class NodeInfo {
+ String httpAddress;
+ }
+
+ static class Info {
+ Map<String, NodeInfo> nodes;
+ }
+
+ @BeforeClass
+ public static void startIndexService()
+ throws InterruptedException, ExecutionException {
+ if (node != null) {
+ // do not start Elasticsearch twice
+ return;
+ }
+ elasticDir = Files.createTempDir();
+ Path elasticDirPath = elasticDir.toPath();
+ Settings settings = Settings.settingsBuilder()
+ .put("cluster.name", "gerrit")
+ .put("node.name", "Gerrit Elasticsearch Test Node")
+ .put("node.local", true)
+ .put("discovery.zen.ping.multicast.enabled", false)
+ .put("index.store.fs.memory.enabled", true)
+ .put("index.gateway.type", "none")
+ .put("index.max_result_window", Integer.MAX_VALUE)
+ .put("gateway.type", "default")
+ .put("http.port", 0)
+ .put("discovery.zen.ping.unicast.hosts", "[\"localhost\"]")
+ .put("path.home", elasticDirPath.toAbsolutePath())
+ .put("path.data", elasticDirPath.resolve("data").toAbsolutePath())
+ .put("path.work", elasticDirPath.resolve("work").toAbsolutePath())
+ .put("path.logs", elasticDirPath.resolve("logs").toAbsolutePath())
+ .build();
+
+ // Start the node
+ node = NodeBuilder.nodeBuilder()
+ .settings(settings)
+ .node();
+
+ // Wait for it to be ready
+ node.client()
+ .admin()
+ .cluster()
+ .prepareHealth()
+ .setWaitForYellowStatus()
+ .execute()
+ .actionGet();
+
+ createIndexes();
+
+ assertThat(node.isClosed()).isFalse();
+ port = getHttpPort();
+ }
+
+ @After
+ public void cleanupIndex() {
+ node.client().admin().indices().prepareDelete("gerrit").execute();
+ createIndexes();
+ }
+
+ @AfterClass
+ public static void stopElasticsearchServer() {
+ if (node != null) {
+ node.close();
+ node = null;
+ }
+ if (elasticDir != null && elasticDir.delete()) {
+ elasticDir = null;
+ }
+ }
+
+ @Override
+ protected Injector createInjector() {
+ Config elasticsearchConfig = new Config(config);
+ InMemoryModule.setDefaults(elasticsearchConfig);
+ elasticsearchConfig.setEnum("index", null, "type", IndexType.ELASTICSEARCH);
+ elasticsearchConfig.setString("index", null, "protocol", "http");
+ elasticsearchConfig.setString("index", null, "hostname", "localhost");
+ elasticsearchConfig.setString("index", null, "port", port);
+ elasticsearchConfig.setString("index", null, "name", "gerrit");
+ elasticsearchConfig.setBoolean("index", "elasticsearch", "test", true);
+ return Guice.createInjector(
+ new InMemoryModule(elasticsearchConfig, notesMigration));
+ }
+
+ private static void createIndexes() {
+ ChangeMapping openChangesMapping =
+ new ChangeMapping(ChangeSchemaDefinitions.INSTANCE.getLatest());
+ ChangeMapping closedChangesMapping =
+ new ChangeMapping(ChangeSchemaDefinitions.INSTANCE.getLatest());
+ openChangesMapping.closedChanges = null;
+ closedChangesMapping.openChanges = null;
+ node.client()
+ .admin()
+ .indices()
+ .prepareCreate("gerrit")
+ .addMapping(OPEN_CHANGES, gson.toJson(openChangesMapping))
+ .addMapping(CLOSED_CHANGES, gson.toJson(closedChangesMapping))
+ .execute()
+ .actionGet();
+ }
+
+ private static String getHttpPort()
+ throws InterruptedException, ExecutionException {
+ String nodes = node.client().admin().cluster()
+ .nodesInfo(new NodesInfoRequest("*")).get().toString();
+ Gson gson = new GsonBuilder()
+ .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+ Info info = gson.fromJson(nodes, Info.class);
+
+ checkState(info.nodes != null && info.nodes.size() == 1);
+ Iterator<NodeInfo> values = info.nodes.values().iterator();
+ String httpAddress = values.next().httpAddress;
+
+ checkState(
+ !Strings.isNullOrEmpty(httpAddress) && httpAddress.indexOf(':') > 0);
+ return httpAddress.substring(httpAddress.indexOf(':') + 1,
+ httpAddress.length());
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index cbe16ed..8f1e283 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -34,6 +34,7 @@
public Map<String, Short> labels;
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
@@ -94,6 +95,12 @@
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) {
message = msg != null && !msg.isEmpty() ? msg : null;
return this;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index d6897b1..29bf00b 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.FileInfo;
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.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -54,15 +55,18 @@
MergeableInfo mergeableOtherBranches() throws RestApiException;
Map<String, List<CommentInfo>> comments() throws RestApiException;
+ Map<String, List<RobotCommentInfo>> robotComments() throws RestApiException;
Map<String, List<CommentInfo>> drafts() throws RestApiException;
List<CommentInfo> commentsAsList() throws RestApiException;
List<CommentInfo> draftsAsList() throws RestApiException;
+ List<RobotCommentInfo> robotCommentsAsList() throws RestApiException;
DraftApi createDraft(DraftInput in) throws RestApiException;
DraftApi draft(String id) throws RestApiException;
CommentApi comment(String id) throws RestApiException;
+ RobotCommentApi robotComment(String id) throws RestApiException;
/**
* Returns patch of revision.
@@ -197,6 +201,12 @@
}
@Override
+ public Map<String, List<RobotCommentInfo>> robotComments()
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public List<CommentInfo> commentsAsList() throws RestApiException {
throw new NotImplementedException();
}
@@ -207,6 +217,12 @@
}
@Override
+ public List<RobotCommentInfo> robotCommentsAsList()
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public Map<String, List<CommentInfo>> drafts() throws RestApiException {
throw new NotImplementedException();
}
@@ -227,6 +243,11 @@
}
@Override
+ public RobotCommentApi robotComment(String id) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public BinaryResult patch() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RobotCommentApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RobotCommentApi.java
new file mode 100644
index 0000000..e1ed107
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RobotCommentApi.java
@@ -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();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
new file mode 100644
index 0000000..a6b7593
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
@@ -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;
+}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
index 2644304..ff060b8 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
@@ -415,6 +415,10 @@
return PatchSet.Id.toId(_number());
}
+ public final boolean isMerge() {
+ return commit().parents().length() > 1;
+ }
+
protected RevisionInfo () {
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffObject.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffObject.java
new file mode 100644
index 0000000..0a1aadd
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffObject.java
@@ -0,0 +1,198 @@
+// 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.client;
+
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+
+/**
+ * Represent an object that can be diffed. This can be either a regular patch
+ * set, the base of a patch set, the parent of a merge, the auto-merge of a
+ * merge or an edit patch set.
+ */
+public class DiffObject {
+ public static final String AUTO_MERGE = "AutoMerge";
+
+ /**
+ * Parses a string that represents a diff object.
+ * <p>
+ * The following string representations are supported:
+ * <ul>
+ * <li>a positive integer: represents a patch set
+ * <li>a negative integer: represents a parent of a merge patch set
+ * <li>'0': represents the edit patch set
+ * <li>empty string or null: represents the parent of a 1-parent patch set,
+ * also called base
+ * <li>'AutoMerge': represents the auto-merge of a merge patch set
+ * </ul>
+ *
+ * @param changeId the ID of the change to which the diff object belongs
+ * @param str the string representation of the diff object
+ * @return the parsed diff object, {@code null} if str cannot be parsed as
+ * diff object
+ */
+ public static DiffObject parse(Change.Id changeId, String str) {
+ if (str == null || str.isEmpty()) {
+ return new DiffObject(false);
+ }
+
+ if (AUTO_MERGE.equals(str)) {
+ return new DiffObject(true);
+ }
+
+ try {
+ return new DiffObject(new PatchSet.Id(changeId, Integer.parseInt(str)));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Create a DiffObject that represents the parent of a 1-parent patch set.
+ */
+ public static DiffObject base() {
+ return new DiffObject(false);
+ }
+
+ /**
+ * Create a DiffObject that represents the auto-merge for a merge patch set.
+ */
+ public static DiffObject autoMerge() {
+ return new DiffObject(true);
+ }
+
+ /**
+ * Create a DiffObject that represents a patch set.
+ */
+ public static DiffObject patchSet(PatchSet.Id psId) {
+ return new DiffObject(psId);
+ }
+
+ private final PatchSet.Id psId;
+ private final boolean autoMerge;
+
+ private DiffObject(PatchSet.Id psId) {
+ this.psId = psId;
+ this.autoMerge = false;
+ }
+
+ private DiffObject(boolean autoMerge) {
+ this.psId = null;
+ this.autoMerge = autoMerge;
+ }
+
+ public boolean isBase() {
+ return psId == null && !autoMerge;
+ }
+
+ public boolean isAutoMerge() {
+ return psId == null && autoMerge;
+ }
+
+ public boolean isBaseOrAutoMerge() {
+ return psId == null;
+ }
+
+ public boolean isPatchSet() {
+ return psId != null && psId.get() > 0;
+ }
+
+ public boolean isParent() {
+ return psId != null && psId.get() < 0;
+ }
+
+ public boolean isEdit() {
+ return psId != null && psId.get() == 0;
+ }
+
+ /**
+ * Returns the DiffObject as PatchSet.Id.
+ *
+ * @return PatchSet.Id with an id > 0 for a regular patch set; PatchSet.Id
+ * with an id < 0 for a parent of a merge; PatchSet.Id with id == 0
+ * for an edit patch set; {@code null} for the base of a 1-parent
+ * patch set and for the auto-merge of a merge patch set
+ */
+ public PatchSet.Id asPatchSetId() {
+ return psId;
+ }
+
+ /**
+ * Returns the parent number for a parent of a merge.
+ *
+ * @return 1-based parent number, 0 if this DiffObject is not a parent of a
+ * merge
+ */
+ public int getParentNum() {
+ if (!isParent()) {
+ return 0;
+ }
+
+ return -psId.get();
+ }
+
+ /**
+ * Returns a string representation of this DiffObject that can be used in
+ * URLs.
+ * <p>
+ * The following string representations are returned:
+ * <ul>
+ * <li>a positive integer for a patch set
+ * <li>a negative integer for a parent of a merge patch set
+ * <li>'0' for the edit patch set
+ * <li>{@code null} for the parent of a 1-parent patch set, also called base
+ * <li>'AutoMerge' for the auto-merge of a merge patch set
+ * </ul>
+ *
+ * @return string representation of this DiffObject
+ */
+ public String asString() {
+ if (autoMerge) {
+ if (Gerrit.getUserPreferences()
+ .defaultBaseForMerges() != DefaultBase.AUTO_MERGE) {
+ return AUTO_MERGE;
+ }
+ return null;
+ }
+
+ if (psId != null) {
+ return psId.getId();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (isPatchSet()) {
+ return "Patch Set " + psId.getId();
+ }
+
+ if (isParent()) {
+ return "Parent " + psId.getId();
+ }
+
+ if (isEdit()) {
+ return "Edit Patch Set";
+ }
+
+ if (isAutoMerge()) {
+ return "Auto Merge";
+ }
+
+ return "Base";
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index cb2ac07..e2aba0a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -108,35 +108,35 @@
import com.google.gwtorm.client.KeyUtil;
public class Dispatcher {
- public static String toPatch(PatchSet.Id diffBase,
+ public static String toPatch(DiffObject diffBase,
PatchSet.Id revision, String fileName) {
return toPatch("", diffBase, revision, fileName, null, 0);
}
- public static String toPatch(PatchSet.Id diffBase,
+ public static String toPatch(DiffObject diffBase,
PatchSet.Id revision, String fileName, DisplaySide side, int line) {
return toPatch("", diffBase, revision, fileName, side, line);
}
- public static String toSideBySide(PatchSet.Id diffBase, Patch.Key id) {
+ public static String toSideBySide(DiffObject diffBase, Patch.Key id) {
return toPatch("sidebyside", diffBase, id);
}
- public static String toSideBySide(PatchSet.Id diffBase,
- PatchSet.Id revision, String fileName) {
+ public static String toSideBySide(DiffObject diffBase, PatchSet.Id revision,
+ String fileName) {
return toPatch("sidebyside", diffBase, revision, fileName, null, 0);
}
- public static String toUnified(PatchSet.Id diffBase,
+ public static String toUnified(DiffObject diffBase,
PatchSet.Id revision, String fileName) {
return toPatch("unified", diffBase, revision, fileName, null, 0);
}
- public static String toUnified(PatchSet.Id diffBase, Patch.Key id) {
+ public static String toUnified(DiffObject diffBase, Patch.Key id) {
return toPatch("unified", diffBase, id);
}
- public static String toPatch(String type, PatchSet.Id diffBase, Patch.Key id) {
+ public static String toPatch(String type, DiffObject diffBase, Patch.Key id) {
return toPatch(type, diffBase, id.getParentKey(), id.get(), null, 0);
}
@@ -145,16 +145,16 @@
}
public static String toEditScreen(PatchSet.Id revision, String fileName, int line) {
- return toPatch("edit", null, revision, fileName, null, line);
+ return toPatch("edit", DiffObject.base(), revision, fileName, null, line);
}
- private static String toPatch(String type, PatchSet.Id diffBase,
+ private static String toPatch(String type, DiffObject diffBase,
PatchSet.Id revision, String fileName, DisplaySide side, int line) {
Change.Id c = revision.getParentKey();
StringBuilder p = new StringBuilder();
p.append("/c/").append(c).append("/");
- if (diffBase != null) {
- p.append(diffBase.get()).append("..");
+ if (diffBase != null && diffBase.asString() != null) {
+ p.append(diffBase.asString()).append("..");
}
p.append(revision.getId()).append("/").append(KeyUtil.encode(fileName));
if (type != null && !type.isEmpty()
@@ -395,7 +395,7 @@
panel = null;
}
Gerrit.display(token, panel == null
- ? new ChangeScreen(id, null, null, false, mode)
+ ? new ChangeScreen(id, DiffObject.base(), null, false, mode)
: new NotFoundScreen());
return;
}
@@ -410,11 +410,14 @@
rest = "";
}
- PatchSet.Id base = null;
+ DiffObject base = DiffObject.base();
PatchSet.Id ps;
int dotdot = psIdStr.indexOf("..");
if (1 <= dotdot) {
- base = new PatchSet.Id(id, Integer.parseInt(psIdStr.substring(0, dotdot)));
+ base = DiffObject.parse(id, psIdStr.substring(0, dotdot));
+ if (base == null) {
+ Gerrit.display(token, new NotFoundScreen());
+ }
psIdStr = psIdStr.substring(dotdot + 2);
}
ps = toPsId(id, psIdStr);
@@ -438,9 +441,7 @@
if (panel == null) {
Gerrit.display(token,
new ChangeScreen(id,
- base != null
- ? String.valueOf(base.get())
- : null,
+ base,
String.valueOf(ps.get()), false, FileTable.Mode.REVIEW));
} else {
Gerrit.display(token, new NotFoundScreen());
@@ -464,7 +465,7 @@
}
private static void patch(String token,
- PatchSet.Id baseId,
+ DiffObject base,
Patch.Key id,
DisplaySide side,
int line,
@@ -477,14 +478,14 @@
if ("".equals(panel) || /* DEPRECATED URL */"cm".equals(panel)) {
if (preferUnified()) {
- unified(token, baseId, id, side, line);
+ unified(token, base, id, side, line);
} else {
- codemirror(token, baseId, id, side, line);
+ codemirror(token, base, id, side, line);
}
} else if ("sidebyside".equals(panel)) {
- codemirror(token, baseId, id, side, line);
+ codemirror(token, base, id, side, line);
} else if ("unified".equals(panel)) {
- unified(token, baseId, id, side, line);
+ unified(token, base, id, side, line);
} else if ("edit".equals(panel)) {
if (!Patch.isMagic(id.get()) || Patch.COMMIT_MSG.equals(id.get())) {
codemirrorForEdit(token, id, line);
@@ -501,24 +502,24 @@
|| (UserAgent.isPortrait() && UserAgent.isMobile());
}
- private static void unified(final String token, final PatchSet.Id baseId,
+ private static void unified(final String token, final DiffObject base,
final Patch.Key id, final DisplaySide side, final int line) {
GWT.runAsync(new AsyncSplit(token) {
@Override
public void onSuccess() {
- Gerrit.display(token,
- new Unified(baseId, id.getParentKey(), id.get(), side, line));
+ Gerrit.display(token, new Unified(base,
+ DiffObject.patchSet(id.getParentKey()), id.get(), side, line));
}
});
}
- private static void codemirror(final String token, final PatchSet.Id baseId,
+ private static void codemirror(final String token, final DiffObject base,
final Patch.Key id, final DisplaySide side, final int line) {
GWT.runAsync(new AsyncSplit(token) {
@Override
public void onSuccess() {
- Gerrit.display(token,
- new SideBySide(baseId, id.getParentKey(), id.get(), side, line));
+ Gerrit.display(token, new SideBySide(base,
+ DiffObject.patchSet(id.getParentKey()), id.get(), side, line));
}
});
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 41fe1a1..2479c87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -15,9 +15,9 @@
messageShowInReviewCategoryAbbrev = Show Abbreviated Name
emailFieldLabel = Email Notifications:
-messageEnabled = Enabled
-messageCCMeOnMyComments = CC Me On Comments I Write
-messageDisabled = Disabled
+messageCCMeOnMyComments = Every Comment
+messageEnabled = Only Comments Left By Others
+messageDisabled = None
defaultBaseForMerges = Default Base For Merges:
autoMerge = Auto Merge
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 6bdeac6..3bfc7da 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -95,13 +95,13 @@
GeneralPreferencesInfo.ReviewCategoryStrategy.ABBREV.name());
emailStrategy = new ListBox();
- emailStrategy.addItem(Util.C.messageEnabled(),
- GeneralPreferencesInfo.EmailStrategy.ENABLED.name());
emailStrategy
.addItem(
Util.C.messageCCMeOnMyComments(),
GeneralPreferencesInfo.EmailStrategy.CC_ON_OWN_COMMENTS
.name());
+ emailStrategy.addItem(Util.C.messageEnabled(),
+ GeneralPreferencesInfo.EmailStrategy.ENABLED.name());
emailStrategy
.addItem(
Util.C.messageDisabled(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 60911e1..436e0c3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -15,10 +15,12 @@
package com.google.gerrit.client.change;
import com.google.gerrit.client.AvatarImage;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.NotFoundScreen;
import com.google.gerrit.client.api.ChangeGlue;
import com.google.gerrit.client.api.ExtensionPanel;
import com.google.gerrit.client.changes.ChangeApi;
@@ -146,7 +148,7 @@
}
private final Change.Id changeId;
- private String base;
+ private DiffObject base;
private String revision;
private ChangeInfo changeInfo;
private boolean hasDraftComments;
@@ -239,10 +241,10 @@
private DeleteFileAction deleteFileAction;
private RenameFileAction renameFileAction;
- public ChangeScreen(Change.Id changeId, String base, String revision,
+ public ChangeScreen(Change.Id changeId, DiffObject base, String revision,
boolean openReplyBox, FileTable.Mode mode) {
this.changeId = changeId;
- this.base = normalize(base);
+ this.base = base;
this.revision = normalize(revision);
this.openReplyBox = openReplyBox;
this.fileTableMode = mode;
@@ -299,9 +301,10 @@
group.addListener(new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
- if (base == null && rev.commit().parents().length() > 1) {
- base = Gerrit.getUserPreferences()
- .defaultBaseForMerges().getBase();
+ if (base.isBase() && rev.isMerge()) {
+ base = DiffObject.parse(info.legacyId(),
+ Gerrit.getUserPreferences()
+ .defaultBaseForMerges().getBase());
}
loadConfigInfo(info, base);
JsArray<MessageInfo> mAr = info.messages();
@@ -915,7 +918,7 @@
int idx = diffBase.getSelectedIndex();
if (0 <= idx) {
String n = diffBase.getValue(idx);
- loadConfigInfo(changeInfo, !n.isEmpty() ? n : null);
+ loadConfigInfo(changeInfo, DiffObject.parse(changeInfo.legacyId(), n));
}
}
@@ -968,13 +971,20 @@
int idx = diffBase.getSelectedIndex();
if (0 <= idx) {
String n = diffBase.getValue(idx);
- loadConfigInfo(changeInfo, !n.isEmpty() ? n : null);
+ loadConfigInfo(changeInfo, DiffObject.parse(changeInfo.legacyId(), n));
}
}
- private void loadConfigInfo(final ChangeInfo info, String base) {
+ private void loadConfigInfo(final ChangeInfo info, DiffObject base) {
final RevisionInfo rev = info.revision(revision);
- RevisionInfo b = resolveRevisionOrPatchSetId(info, base, null);
+ if (base.isAutoMerge() && !initCurrentRevision(info).isMerge()) {
+ Gerrit.display(getToken(), new NotFoundScreen());
+ }
+
+ updateToken(info, base, rev);
+
+ RevisionInfo baseRev =
+ resolveRevisionOrPatchSetId(info, base.asString(), null);
CallbackGroup group = new CallbackGroup();
Timestamp lastReply = myLastReply(info);
@@ -984,9 +994,9 @@
RevisionInfo p = RevisionInfo.findEditParentRevision(
info.revisions().values());
List<NativeMap<JsArray<CommentInfo>>> comments = loadComments(p, group);
- loadFileList(b, rev, lastReply, group, comments, null);
+ loadFileList(base, baseRev, rev, lastReply, group, comments, null);
} else {
- loadDiff(b, rev, lastReply, group);
+ loadDiff(base, baseRev, rev, lastReply, group);
}
group.addListener(new AsyncCallback<Void>() {
@Override
@@ -1025,6 +1035,21 @@
});
}
+ private void updateToken(ChangeInfo info, DiffObject base, RevisionInfo rev) {
+ StringBuilder token = new StringBuilder("/c/")
+ .append(info._number())
+ .append("/");
+ if (base.asString() != null) {
+ token.append(base.asString())
+ .append("..");
+ }
+ if (base.asString() != null
+ || !rev.name().equals(info.currentRevision())) {
+ token.append(rev._number());
+ }
+ setToken(token.toString());
+ }
+
static Timestamp myLastReply(ChangeInfo info) {
if (Gerrit.isSignedIn() && info.messages() != null) {
int self = Gerrit.getUserAccount()._accountId();
@@ -1038,11 +1063,11 @@
return null;
}
- private void loadDiff(RevisionInfo base, RevisionInfo rev,
+ private void loadDiff(DiffObject base, RevisionInfo baseRev, RevisionInfo rev,
Timestamp myLastReply, CallbackGroup group) {
List<NativeMap<JsArray<CommentInfo>>> comments = loadComments(rev, group);
List<NativeMap<JsArray<CommentInfo>>> drafts = loadDrafts(rev, group);
- loadFileList(base, rev, myLastReply, group, comments, drafts);
+ loadFileList(base, baseRev, rev, myLastReply, group, comments, drafts);
if (Gerrit.isSignedIn() && fileTableMode == FileTable.Mode.REVIEW) {
ChangeApi.revision(changeId.get(), rev.name())
@@ -1061,19 +1086,19 @@
}
}
- private void loadFileList(final RevisionInfo base, final RevisionInfo rev,
- final Timestamp myLastReply, CallbackGroup group,
+ private void loadFileList(final DiffObject base, final RevisionInfo baseRev,
+ final RevisionInfo rev, final Timestamp myLastReply, CallbackGroup group,
final List<NativeMap<JsArray<CommentInfo>>> comments,
final List<NativeMap<JsArray<CommentInfo>>> drafts) {
DiffApi.list(changeId.get(),
rev.name(),
- base,
+ baseRev,
group.add(
new AsyncCallback<NativeMap<FileInfo>>() {
@Override
public void onSuccess(NativeMap<FileInfo> m) {
files.set(
- base != null ? new PatchSet.Id(changeId, base._number()) : null,
+ base,
new PatchSet.Id(changeId, rev._number()),
style, reply, fileTableMode, edit != null);
files.setValue(m, myLastReply,
@@ -1475,12 +1500,12 @@
RevisionInfo r = list.get(i);
diffBase.addItem(
r.id() + ": " + r.name().substring(0, 6),
- r.name());
+ r.id());
if (r.name().equals(revision)) {
SelectElement.as(diffBase.getElement()).getOptions()
.getItem(diffBase.getItemCount() - 1).setDisabled(true);
}
- if (base != null && base.equals(String.valueOf(r._number()))) {
+ if (base.isPatchSet() && base.asPatchSetId().get() == r._number()) {
selectedIdx = diffBase.getItemCount() - 1;
}
}
@@ -1488,15 +1513,15 @@
RevisionInfo rev = info.revisions().get(revision);
JsArray<CommitInfo> parents = rev.commit().parents();
if (parents.length() > 1) {
- diffBase.addItem(Util.C.autoMerge(), "");
+ diffBase.addItem(Util.C.autoMerge(), DiffObject.AUTO_MERGE);
for (int i = 0; i < parents.length(); i++) {
int parentNum = i + 1;
diffBase.addItem(Util.M.diffBaseParent(parentNum),
String.valueOf(-parentNum));
}
- int parentNum = toParentNum(base);
- if (parentNum > 0) {
- selectedIdx = list.length() + parentNum;
+
+ if (base.isParent()) {
+ selectedIdx = list.length() + base.getParentNum();
}
} else {
diffBase.addItem(Util.C.baseDiffItem(), "");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index 3a85b26..a95270b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.client.FormatUtil.formatBytes;
import static com.google.gerrit.client.FormatUtil.formatPercentage;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
@@ -182,7 +183,7 @@
return null;
}
- private PatchSet.Id base;
+ private DiffObject base;
private PatchSet.Id curr;
private MyTable table;
private boolean register;
@@ -199,7 +200,7 @@
R.css().ensureInjected();
}
- public void set(PatchSet.Id base, PatchSet.Id curr, ChangeScreen.Style style,
+ public void set(DiffObject base, PatchSet.Id curr, ChangeScreen.Style style,
Widget replyButton, Mode mode, boolean editExists) {
this.base = base;
this.curr = curr;
@@ -340,7 +341,7 @@
});
setSavePointerId(
- (base != null ? base.toString() + ".." : "")
+ (!base.isBase() ? base.asString() + ".." : "")
+ curr.toString());
}
@@ -789,9 +790,9 @@
for (CommentInfo c : Natives.asList(list)) {
if (c.side() == Side.REVISION) {
result.push(c);
- } else if (base == null && !c.hasParent()) {
+ } else if (base.isBaseOrAutoMerge() && !c.hasParent()) {
result.push(c);
- } else if (base != null && c.parent() == -base.get()) {
+ } else if (base.isParent() && c.parent() == base.getParentNum()) {
result.push(c);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
index 2f3ead3..4c01941 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.patches.SkippedLine;
@@ -40,7 +41,7 @@
/** Tracks comment widgets for {@link DiffScreen}. */
abstract class CommentManager {
- private final PatchSet.Id base;
+ private final DiffObject base;
private final PatchSet.Id revision;
private final String path;
private final CommentLinkProcessor commentLinkProcessor;
@@ -55,7 +56,7 @@
CommentManager(
DiffScreen host,
- PatchSet.Id base,
+ DiffObject base,
PatchSet.Id revision,
String path,
CommentLinkProcessor clp,
@@ -129,29 +130,30 @@
}
Side getStoredSideFromDisplaySide(DisplaySide side) {
- if (side == DisplaySide.A && (base == null || base.get() < 0)) {
+ if (side == DisplaySide.A && base.isBaseOrAutoMerge() || base.isParent()) {
return Side.PARENT;
}
return Side.REVISION;
}
int getParentNumFromDisplaySide(DisplaySide side) {
- if (side == DisplaySide.A && base != null && base.get() < 0) {
- return -base.get();
+ if (side == DisplaySide.A) {
+ return base.getParentNum();
}
return 0;
}
PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
- if (side == DisplaySide.A && base != null && base.get() >= 0) {
- return base;
+ if (side == DisplaySide.A && (base.isPatchSet() || base.isEdit())) {
+ return base.asPatchSetId();
}
return revision;
}
DisplaySide displaySide(CommentInfo info, DisplaySide forSide) {
if (info.side() == Side.PARENT) {
- return (base == null || base.get() < 0) ? DisplaySide.A : null;
+ return (base.isBaseOrAutoMerge() || base.isParent())
+ ? DisplaySide.A : null;
}
return forSide;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
index ce1d294..0b8e141 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentApi;
import com.google.gerrit.client.changes.CommentInfo;
@@ -31,7 +32,7 @@
/** Collection of published and draft comments loaded from the server. */
class CommentsCollections {
private final String path;
- private final PatchSet.Id base;
+ private final DiffObject base;
private final PatchSet.Id revision;
private NativeMap<JsArray<CommentInfo>> publishedBaseAll;
private NativeMap<JsArray<CommentInfo>> publishedRevisionAll;
@@ -40,28 +41,28 @@
JsArray<CommentInfo> draftsBase;
JsArray<CommentInfo> draftsRevision;
- CommentsCollections(PatchSet.Id base, PatchSet.Id revision, String path) {
+ CommentsCollections(DiffObject base, PatchSet.Id revision, String path) {
this.path = path;
this.base = base;
this.revision = revision;
}
void load(CallbackGroup group) {
- if (base != null && base.get() > 0) {
- CommentApi.comments(base, group.add(publishedBase()));
+ if (base.isPatchSet()) {
+ CommentApi.comments(base.asPatchSetId(), group.add(publishedBase()));
}
CommentApi.comments(revision, group.add(publishedRevision()));
if (Gerrit.isSignedIn()) {
- if (base != null && base.get() > 0) {
- CommentApi.drafts(base, group.add(draftsBase()));
+ if (base.isPatchSet()) {
+ CommentApi.drafts(base.asPatchSetId(), group.add(draftsBase()));
}
CommentApi.drafts(revision, group.add(draftsRevision()));
}
}
boolean hasCommentForPath(String filePath) {
- if (base != null && base.get() > 0) {
+ if (base.isPatchSet()) {
JsArray<CommentInfo> forBase = publishedBaseAll.get(filePath);
if (forBase != null && forBase.length() > 0) {
return true;
@@ -110,9 +111,9 @@
for (CommentInfo c : Natives.asList(list)) {
if (c.side() == Side.REVISION) {
result.push(c);
- } else if (base == null && !c.hasParent()) {
+ } else if (base.isBaseOrAutoMerge() && !c.hasParent()) {
result.push(c);
- } else if (base != null && c.parent() == -base.get()) {
+ } else if (base.isParent() && c.parent() == base.getParentNum()) {
result.push(c);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
index de19b35..a22d4bd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.WHOLE_FILE_CONTEXT;
import static java.lang.Double.POSITIVE_INFINITY;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.DiffPreferences;
@@ -96,7 +97,7 @@
}
private final Change.Id changeId;
- final PatchSet.Id base;
+ final DiffObject base;
final PatchSet.Id revision;
final String path;
final DiffPreferences prefs;
@@ -123,15 +124,15 @@
Header header;
DiffScreen(
- PatchSet.Id base,
- PatchSet.Id revision,
+ DiffObject base,
+ DiffObject revision,
String path,
DisplaySide startSide,
int startLine,
DiffView diffScreenType) {
this.base = base;
- this.revision = revision;
- this.changeId = revision.getParentKey();
+ this.revision = revision.asPatchSetId();
+ this.changeId = revision.asPatchSetId().getParentKey();
this.path = path;
this.startSide = startSide;
this.startLine = startLine;
@@ -173,7 +174,7 @@
}));
DiffApi.diff(revision, path)
- .base(base)
+ .base(base.asPatchSetId())
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
@@ -789,11 +790,10 @@
group.addListener(new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
- String b = base != null ? String.valueOf(base.get()) : null;
String rev = String.valueOf(revision.get());
Gerrit.display(
- PageLinks.toChange(changeId, b, rev),
- new ChangeScreen(changeId, b, rev, openReplyBox,
+ PageLinks.toChange(changeId, base.asString(), rev),
+ new ChangeScreen(changeId, base, rev, openReplyBox,
FileTable.Mode.REVIEW));
}
});
@@ -901,7 +901,7 @@
String nextPath = header.getNextPath();
if (nextPath != null) {
DiffApi.diff(revision, nextPath)
- .base(base)
+ .base(base.asPatchSetId())
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
@@ -924,7 +924,7 @@
void reloadDiffInfo() {
final int id = ++reloadVersionId;
DiffApi.diff(revision, path)
- .base(base)
+ .base(base.asPatchSetId())
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index 392ad2f..54b55f04 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
@@ -66,11 +67,12 @@
private ChangeType changeType;
Scrollbar scrollbar;
- DiffTable(DiffScreen parent, PatchSet.Id base, PatchSet.Id revision, String path) {
- patchSetSelectBoxA = new PatchSetSelectBox(
- parent, DisplaySide.A, revision.getParentKey(), base, path);
- patchSetSelectBoxB = new PatchSetSelectBox(
- parent, DisplaySide.B, revision.getParentKey(), revision, path);
+ DiffTable(DiffScreen parent, DiffObject base, DiffObject revision,
+ String path) {
+ patchSetSelectBoxA = new PatchSetSelectBox(parent, DisplaySide.A,
+ revision.asPatchSetId().getParentKey(), base, path);
+ patchSetSelectBoxB = new PatchSetSelectBox(parent, DisplaySide.B,
+ revision.asPatchSetId().getParentKey(), revision, path);
PatchSetSelectBox.link(patchSetSelectBoxA, patchSetSelectBoxB);
this.scrollbar = new Scrollbar(this);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
index 3033cbb1..f3b9886 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.DiffPreferences;
@@ -87,7 +88,7 @@
@UiField Image preferences;
private final KeyCommandSet keys;
- private final PatchSet.Id base;
+ private final DiffObject base;
private final PatchSet.Id patchSetId;
private final String path;
private final DiffView diffScreenType;
@@ -99,12 +100,12 @@
private PreferencesAction prefsAction;
private ReviewedState reviewedState;
- Header(KeyCommandSet keys, PatchSet.Id base, PatchSet.Id patchSetId,
+ Header(KeyCommandSet keys, DiffObject base, DiffObject patchSetId,
String path, DiffView diffSreenType, DiffPreferences prefs) {
initWidget(uiBinder.createAndBindUi(this));
this.keys = keys;
this.base = base;
- this.patchSetId = patchSetId;
+ this.patchSetId = patchSetId.asPatchSetId();
this.path = path;
this.diffScreenType = diffSreenType;
this.prefs = prefs;
@@ -113,9 +114,9 @@
reviewed.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
SafeHtml.setInnerHTML(filePath, formatPath(path));
- up.setTargetHistoryToken(PageLinks.toChange(
- patchSetId.getParentKey(),
- base != null ? base.getId() : null, patchSetId.getId()));
+ up.setTargetHistoryToken(
+ PageLinks.toChange(patchSetId.asPatchSetId().getParentKey(),
+ base.asString(), patchSetId.asPatchSetId().getId()));
}
public static SafeHtml formatPath(String path) {
@@ -147,16 +148,17 @@
@Override
protected void onLoad() {
- DiffApi.list(patchSetId, base, new GerritCallback<NativeMap<FileInfo>>() {
- @Override
- public void onSuccess(NativeMap<FileInfo> result) {
- files = result.values();
- FileInfo.sortFileInfoByPath(files);
- fileNumber.setInnerText(
- Integer.toString(Natives.asList(files).indexOf(result.get(path)) + 1));
- fileCount.setInnerText(Integer.toString(files.length()));
- }
- });
+ DiffApi.list(patchSetId, base.asPatchSetId(),
+ new GerritCallback<NativeMap<FileInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<FileInfo> result) {
+ files = result.values();
+ FileInfo.sortFileInfoByPath(files);
+ fileNumber.setInnerText(Integer
+ .toString(Natives.asList(files).indexOf(result.get(path)) + 1));
+ fileCount.setInnerText(Integer.toString(files.length()));
+ }
+ });
if (Gerrit.isSignedIn()) {
ChangeApi.revision(patchSetId).view("files")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
index d45b8d8..b07a199 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.blame.BlameInfo;
@@ -67,13 +68,13 @@
private String path;
private Change.Id changeId;
private PatchSet.Id revision;
- private PatchSet.Id idActive;
+ private DiffObject idActive;
private PatchSetSelectBox other;
PatchSetSelectBox(DiffScreen parent,
DisplaySide side,
Change.Id changeId,
- PatchSet.Id revision,
+ DiffObject diffObject,
String path) {
initWidget(uiBinder.createAndBindUi(this));
icon.setTitle(PatchUtil.C.addFileCommentToolTip());
@@ -83,8 +84,8 @@
this.side = side;
this.sideA = side == DisplaySide.A;
this.changeId = changeId;
- this.revision = revision;
- this.idActive = (sideA && revision == null) ? null : revision;
+ this.revision = diffObject.asPatchSetId();
+ this.idActive = diffObject;
this.path = path;
}
@@ -93,19 +94,22 @@
InlineHyperlink selectedLink = null;
if (sideA) {
if (parents <= 1) {
- InlineHyperlink link = createLink(PatchUtil.C.patchBase(), null);
+ InlineHyperlink link =
+ createLink(PatchUtil.C.patchBase(), DiffObject.base());
linkPanel.add(link);
selectedLink = link;
} else {
for (int i = parents; i > 0; i--) {
PatchSet.Id id = new PatchSet.Id(changeId, -i);
- InlineHyperlink link = createLink(Util.M.diffBaseParent(i), id);
+ InlineHyperlink link =
+ createLink(Util.M.diffBaseParent(i), DiffObject.patchSet(id));
linkPanel.add(link);
if (revision != null && id.equals(revision)) {
selectedLink = link;
}
}
- InlineHyperlink link = createLink(Util.C.autoMerge(), null);
+ InlineHyperlink link =
+ createLink(Util.C.autoMerge(), DiffObject.autoMerge());
linkPanel.add(link);
if (selectedLink == null) {
selectedLink = link;
@@ -115,7 +119,7 @@
for (int i = 0; i < list.length(); i++) {
RevisionInfo r = list.get(i);
InlineHyperlink link = createLink(r.id(),
- new PatchSet.Id(changeId, r._number()));
+ DiffObject.patchSet(new PatchSet.Id(changeId, r._number())));
linkPanel.add(link);
if (revision != null && r.id().equals(revision.getId())) {
selectedLink = link;
@@ -131,8 +135,8 @@
if (!Patch.isMagic(path)) {
linkPanel.add(createDownloadLink());
}
- if (!binary && open && idActive != null && Gerrit.isSignedIn()) {
- if ((editExists && idActive.get() == 0)
+ if (!binary && open && !idActive.isBaseOrAutoMerge() && Gerrit.isSignedIn()) {
+ if ((editExists && idActive.isEdit())
|| (!editExists && current)) {
linkPanel.add(createEditIcon());
}
@@ -172,7 +176,9 @@
}
private Widget createEditIcon() {
- PatchSet.Id id = (idActive == null) ? other.idActive : idActive;
+ PatchSet.Id id = idActive.isBaseOrAutoMerge()
+ ? other.idActive.asPatchSetId()
+ : idActive.asPatchSetId();
Anchor anchor = new Anchor(
new ImageResourceRenderer().render(Gerrit.RESOURCES.edit()),
"#" + Dispatcher.toEditScreen(id, path));
@@ -192,27 +198,29 @@
b.other = a;
}
- private InlineHyperlink createLink(String label, PatchSet.Id id) {
+ private InlineHyperlink createLink(String label, DiffObject id) {
assert other != null;
if (sideA) {
- assert other.idActive != null;
+ assert !other.idActive.isBaseOrAutoMerge();
}
- PatchSet.Id diffBase = sideA ? id : other.idActive;
- PatchSet.Id revision = sideA ? other.idActive : id;
+ DiffObject diffBase = sideA ? id : other.idActive;
+ DiffObject revision = sideA ? other.idActive : id;
return new InlineHyperlink(label,
parent.isSideBySide()
- ? Dispatcher.toSideBySide(diffBase, revision, path)
- : Dispatcher.toUnified(diffBase, revision, path));
+ ? Dispatcher.toSideBySide(diffBase, revision.asPatchSetId(), path)
+ : Dispatcher.toUnified(diffBase, revision.asPatchSetId(), path));
}
private Anchor createDownloadLink() {
- PatchSet.Id id = (idActive == null) ? other.idActive : idActive;
- String sideURL = (idActive == null) ? "1" : "0";
+ DiffObject diffObject = idActive.isBaseOrAutoMerge()
+ ? other.idActive : idActive;
+ String sideURL = idActive.isBaseOrAutoMerge() ? "1" : "0";
String base = GWT.getHostPageBaseURL() + "cat/";
Anchor anchor = new Anchor(
new ImageResourceRenderer().render(Gerrit.RESOURCES.downloadIcon()),
- base + KeyUtil.encode(id + "," + path) + "^" + sideURL);
+ base + KeyUtil.encode(diffObject.asPatchSetId() + "," + path) + "^"
+ + sideURL);
anchor.setTitle(PatchUtil.C.download());
return anchor;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
index dbe7e5d..6e2120a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
@@ -16,6 +16,7 @@
import static java.lang.Double.POSITIVE_INFINITY;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
@@ -25,7 +26,6 @@
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -69,8 +69,8 @@
private SideBySideCommentManager commentManager;
public SideBySide(
- PatchSet.Id base,
- PatchSet.Id revision,
+ DiffObject base,
+ DiffObject revision,
String path,
DisplaySide startSide,
int startLine) {
@@ -192,9 +192,8 @@
cmA = newCm(diff.metaA(), diff.textA(), diffTable.cmA);
cmB = newCm(diff.metaB(), diff.textB(), diffTable.cmB);
- boolean reviewingBase = base == null;
- getDiffTable().setUpBlameIconA(cmA, reviewingBase,
- reviewingBase ? revision : base, path);
+ getDiffTable().setUpBlameIconA(cmA, base.isBaseOrAutoMerge(),
+ base.isBaseOrAutoMerge() ? revision : base.asPatchSetId(), path);
getDiffTable().setUpBlameIconB(cmB, revision, path);
cmA.extras().side(DisplaySide.A);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
index bcb7dac..fab6e6b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.ui.CommentLinkProcessor;
@@ -29,7 +30,7 @@
/** Tracks comment widgets for {@link SideBySide}. */
class SideBySideCommentManager extends CommentManager {
SideBySideCommentManager(SideBySide host,
- PatchSet.Id base, PatchSet.Id revision,
+ DiffObject base, PatchSet.Id revision,
String path,
CommentLinkProcessor clp,
boolean open) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideTable.java
index 2296796..5e8d7cc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideTable.java
@@ -14,8 +14,8 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.resources.client.CssResource;
@@ -46,7 +46,7 @@
private boolean visibleA;
- SideBySideTable(SideBySide parent, PatchSet.Id base, PatchSet.Id revision,
+ SideBySideTable(SideBySide parent, DiffObject base, DiffObject revision,
String path) {
super(parent, base, revision, path);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
index a231580..566d87c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Unified.java
@@ -16,6 +16,7 @@
import static java.lang.Double.POSITIVE_INFINITY;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.diff.UnifiedChunkManager.LineRegionInfo;
@@ -25,7 +26,6 @@
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
@@ -69,8 +69,8 @@
private boolean autoHideDiffTableHeader;
public Unified(
- PatchSet.Id base,
- PatchSet.Id revision,
+ DiffObject base,
+ DiffObject revision,
String path,
DisplaySide startSide,
int startLine) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedCommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedCommentManager.java
index 8968bc7..21356fc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedCommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedCommentManager.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
@@ -43,7 +44,7 @@
private final Map<Integer, CommentGroup> duplicates;
UnifiedCommentManager(Unified host,
- PatchSet.Id base, PatchSet.Id revision,
+ DiffObject base, PatchSet.Id revision,
String path,
CommentLinkProcessor clp,
boolean open) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedTable.java
index 72b3e49..e3317c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UnifiedTable.java
@@ -14,7 +14,7 @@
package com.google.gerrit.client.diff;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.client.DiffObject;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.resources.client.CssResource;
@@ -45,7 +45,7 @@
@UiField Element cm;
@UiField static DiffTableStyle style;
- UnifiedTable(Unified parent, PatchSet.Id base, PatchSet.Id revision,
+ UnifiedTable(Unified parent, DiffObject base, DiffObject revision,
String path) {
super(parent, base, revision, path);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 4a01128..111dfc9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -18,11 +18,13 @@
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.common.errors.UpdateParentFailedException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -89,10 +91,15 @@
@Override
public final T call() throws NoSuchProjectException, IOException,
ConfigInvalidException, InvalidNameException, NoSuchGroupException,
- OrmException, UpdateParentFailedException {
+ OrmException, UpdateParentFailedException, PermissionDeniedException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
+ Capable r = projectControl.canPushToAtLeastOneRef();
+ if (r != Capable.OK) {
+ throw new PermissionDeniedException(r.getMessage());
+ }
+
try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
diff --git a/gerrit-index/BUCK b/gerrit-index/BUCK
new file mode 100644
index 0000000..ea97f88
--- /dev/null
+++ b/gerrit-index/BUCK
@@ -0,0 +1,13 @@
+java_library(
+ name = 'index',
+ srcs = glob(['src/main/java/**/*.java']),
+ deps = [
+ '//gerrit-extension-api:api',
+ '//gerrit-server:server',
+ '//gerrit-patch-jgit:server',
+ '//lib/guice:guice',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib:guava',
+ ],
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-index/BUILD b/gerrit-index/BUILD
new file mode 100644
index 0000000..119d5c4
--- /dev/null
+++ b/gerrit-index/BUILD
@@ -0,0 +1,13 @@
+java_library(
+ name = 'index',
+ srcs = glob(['src/main/java/**/*.java']),
+ deps = [
+ '//gerrit-extension-api:api',
+ '//gerrit-server:server',
+ '//gerrit-patch-jgit:server',
+ '//lib/guice:guice',
+ '//lib/jgit/org.eclipse.jgit:jgit',
+ '//lib:guava',
+ ],
+ visibility = ['//visibility:public'],
+)
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/GerritIndexStatus.java b/gerrit-index/src/main/java/com/google/gerrit/index/GerritIndexStatus.java
similarity index 84%
rename from gerrit-lucene/src/main/java/com/google/gerrit/lucene/GerritIndexStatus.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/GerritIndexStatus.java
index f43e385..cafd30e 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/GerritIndexStatus.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/GerritIndexStatus.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.lucene;
+package com.google.gerrit.index;
import com.google.common.primitives.Ints;
import com.google.gerrit.server.config.SitePaths;
@@ -24,13 +24,13 @@
import java.io.IOException;
-class GerritIndexStatus {
+public class GerritIndexStatus {
private static final String SECTION = "index";
private static final String KEY_READY = "ready";
private final FileBasedConfig cfg;
- GerritIndexStatus(SitePaths sitePaths)
+ public GerritIndexStatus(SitePaths sitePaths)
throws ConfigInvalidException, IOException {
cfg = new FileBasedConfig(
sitePaths.index_dir.resolve("gerrit_index.config").toFile(),
@@ -39,16 +39,16 @@
convertLegacyConfig();
}
- void setReady(String indexName, int version, boolean ready) {
+ public void setReady(String indexName, int version, boolean ready) {
cfg.setBoolean(SECTION, indexDirName(indexName, version), KEY_READY, ready);
}
- boolean getReady(String indexName, int version) {
+ public boolean getReady(String indexName, int version) {
return cfg.getBoolean(SECTION, indexDirName(indexName, version), KEY_READY,
false);
}
- void save() throws IOException {
+ public void save() throws IOException {
cfg.save();
}
@@ -62,8 +62,8 @@
if (ready != null) {
dirty = false;
cfg.unset(SECTION, subsection, KEY_READY);
- cfg.setString(SECTION,
- indexDirName(ChangeSchemaDefinitions.NAME, v), KEY_READY, ready);
+ cfg.setString(SECTION, indexDirName(ChangeSchemaDefinitions.NAME, v),
+ KEY_READY, ready);
}
}
}
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/IndexUtils.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexUtils.java
new file mode 100644
index 0000000..f00f5c2
--- /dev/null
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexUtils.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2013 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.index;
+
+import static com.google.gerrit.server.index.change.ChangeField.CHANGE;
+import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
+import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.QueryOptions;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+public final class IndexUtils {
+ public static final Map<String, String> CUSTOM_CHAR_MAPPING =
+ ImmutableMap.of("_", " ", ".", " ");
+
+ public static void setReady(SitePaths sitePaths, String name, int version,
+ boolean ready) throws IOException {
+ try {
+ GerritIndexStatus cfg = new GerritIndexStatus(sitePaths);
+ cfg.setReady(name, version, ready);
+ cfg.save();
+ } catch (ConfigInvalidException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public static Set<String> fields(QueryOptions opts) {
+ // Ensure we request enough fields to construct a ChangeData. We need both
+ // change ID and project, which can either come via the Change field or
+ // separate fields.
+ Set<String> fs = opts.fields();
+ if (fs.contains(CHANGE.getName())) {
+ // A Change is always sufficient.
+ return fs;
+ }
+ if (fs.contains(PROJECT.getName()) && fs.contains(LEGACY_ID.getName())) {
+ return fs;
+ }
+ return Sets.union(fs,
+ ImmutableSet.of(LEGACY_ID.getName(), PROJECT.getName()));
+ }
+
+ private IndexUtils() {
+ // hide default constructor
+ }
+}
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/SingleVersionModule.java b/gerrit-index/src/main/java/com/google/gerrit/index/SingleVersionModule.java
new file mode 100644
index 0000000..d547b06
--- /dev/null
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SingleVersionModule.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2013 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.index;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.index.Index;
+import com.google.gerrit.server.index.IndexDefinition;
+import com.google.gerrit.server.index.Schema;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+@Singleton
+public class SingleVersionModule extends LifecycleModule {
+ static final String SINGLE_VERSIONS = "IndexModule/SingleVersions";
+
+ private final Map<String, Integer> singleVersions;
+
+ public SingleVersionModule(Map<String, Integer> singleVersions) {
+ this.singleVersions = singleVersions;
+ }
+
+ @Override
+ public void configure() {
+ listener().to(SingleVersionListener.class);
+ bind(new TypeLiteral<Map<String, Integer>>() {})
+ .annotatedWith(Names.named(SINGLE_VERSIONS))
+ .toInstance(singleVersions);
+ }
+
+ @Singleton
+ static class SingleVersionListener implements LifecycleListener {
+ private final Set<String> disabled;
+ private final Collection<IndexDefinition<?, ?, ?>> defs;
+ private final Map<String, Integer> singleVersions;
+
+ @Inject
+ SingleVersionListener(
+ @GerritServerConfig Config cfg,
+ Collection<IndexDefinition<?, ?, ?>> defs,
+ @Named(SINGLE_VERSIONS) Map<String, Integer> singleVersions) {
+ this.defs = defs;
+ this.singleVersions = singleVersions;
+
+ disabled = ImmutableSet.copyOf(
+ cfg.getStringList("index", null, "testDisable"));
+ }
+
+ @Override
+ public void start() {
+ for (IndexDefinition<?, ?, ?> def : defs) {
+ start(def);
+ }
+ }
+
+ private <K, V, I extends Index<K, V>> void start(
+ IndexDefinition<K, V, I> def) {
+ if (disabled.contains(def.getName())) {
+ return;
+ }
+ Schema<V> schema;
+ Integer v = singleVersions.get(def.getName());
+ if (v == null) {
+ schema = def.getLatest();
+ } else {
+ schema = def.getSchemas().get(v);
+ if (schema == null) {
+ throw new ProvisionException(String.format(
+ "Unrecognized %s schema version: %s", def.getName(), v));
+ }
+ }
+ I index = def.getIndexFactory().create(schema);
+ def.getIndexCollection().setSearchIndex(index);
+ def.getIndexCollection().addWriteIndex(index);
+ }
+
+ @Override
+ public void stop() {
+ // Do nothing; indexes are closed by IndexCollection.
+ }
+ }
+}
\ No newline at end of file
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
index 771a021..f4f097c 100644
--- a/gerrit-lucene/BUCK
+++ b/gerrit-lucene/BUCK
@@ -27,6 +27,7 @@
'//gerrit-extension-api:api',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
+ '//gerrit-index:index',
'//lib:guava',
'//lib:gwtorm',
'//lib/guice:guice',
diff --git a/gerrit-lucene/BUILD b/gerrit-lucene/BUILD
index 2f1cba7..de010eb 100644
--- a/gerrit-lucene/BUILD
+++ b/gerrit-lucene/BUILD
@@ -25,6 +25,7 @@
'//gerrit-common:annotations',
'//gerrit-common:server',
'//gerrit-extension-api:api',
+ '//gerrit-index:index',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//lib:guava',
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index eb0dfaa..e869afb 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -25,6 +25,7 @@
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.index.IndexUtils;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldDef.FillArgs;
@@ -51,7 +52,6 @@
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -75,17 +75,6 @@
return f.getName() + "_SORT";
}
- public static void setReady(SitePaths sitePaths, String name, int version,
- boolean ready) throws IOException {
- try {
- GerritIndexStatus cfg = new GerritIndexStatus(sitePaths);
- cfg.setReady(name, version, ready);
- cfg.save();
- } catch (ConfigInvalidException e) {
- throw new IOException(e);
- }
- }
-
private final Schema<V> schema;
private final SitePaths sitePaths;
private final Directory dir;
@@ -198,7 +187,7 @@
@Override
public void markReady(boolean ready) throws IOException {
- setReady(sitePaths, name, schema.getVersion(), ready);
+ IndexUtils.setReady(sitePaths, name, schema.getVersion(), ready);
}
@Override
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 2decff5..4775ac4 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -18,7 +18,6 @@
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
import static com.google.gerrit.lucene.LuceneVersionManager.CHANGES_PREFIX;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
-import static com.google.gerrit.server.index.change.ChangeField.CHANGE;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
@@ -27,7 +26,6 @@
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
@@ -35,6 +33,7 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.IndexUtils;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -317,7 +316,7 @@
throw new OrmException("interrupted");
}
- final Set<String> fields = fields(opts);
+ final Set<String> fields = IndexUtils.fields(opts);
return new ChangeDataResults(
executor.submit(new Callable<List<Document>>() {
@Override
@@ -405,22 +404,6 @@
}
}
- private Set<String> fields(QueryOptions opts) {
- // Ensure we request enough fields to construct a ChangeData. We need both
- // change ID and project, which can either come via the Change field or
- // separate fields.
- Set<String> fs = opts.fields();
- if (fs.contains(CHANGE.getName())) {
- // A Change is always sufficient.
- return fs;
- }
- if (fs.contains(PROJECT.getName()) && fs.contains(LEGACY_ID.getName())) {
- return fs;
- }
- return Sets.union(fs,
- ImmutableSet.of(LEGACY_ID.getName(), PROJECT.getName()));
- }
-
private static Multimap<String, IndexableField> fields(Document doc,
Set<String> fields) {
Multimap<String, IndexableField> stored =
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index f5d5146..58890176 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -15,37 +15,23 @@
package com.google.gerrit.lucene;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.SingleVersionModule;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.inject.Inject;
import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
import org.apache.lucene.search.BooleanQuery;
import org.eclipse.jgit.lib.Config;
-import java.util.Collection;
import java.util.Map;
-import java.util.Set;
public class LuceneIndexModule extends LifecycleModule {
- private static final String SINGLE_VERSIONS =
- "LuceneIndexModule/SingleVersions";
-
public static LuceneIndexModule singleVersionAllLatest(int threads) {
return new LuceneIndexModule(ImmutableMap.<String, Integer> of(), threads);
}
@@ -86,7 +72,7 @@
if (singleVersions == null) {
install(new MultiVersionModule());
} else {
- install(new SingleVersionModule());
+ install(new SingleVersionModule(singleVersions));
}
}
@@ -104,66 +90,4 @@
listener().to(LuceneVersionManager.class);
}
}
-
- private class SingleVersionModule extends LifecycleModule {
- @Override
- public void configure() {
- listener().to(SingleVersionListener.class);
- bind(new TypeLiteral<Map<String, Integer>>() {})
- .annotatedWith(Names.named(SINGLE_VERSIONS))
- .toInstance(singleVersions);
- }
- }
-
- @Singleton
- static class SingleVersionListener implements LifecycleListener {
- private final Set<String> disabled;
- private final Collection<IndexDefinition<?, ?, ?>> defs;
- private final Map<String, Integer> singleVersions;
-
- @Inject
- SingleVersionListener(
- @GerritServerConfig Config cfg,
- Collection<IndexDefinition<?, ?, ?>> defs,
- @Named(SINGLE_VERSIONS) Map<String, Integer> singleVersions) {
- this.defs = defs;
- this.singleVersions = singleVersions;
-
- disabled = ImmutableSet.copyOf(
- cfg.getStringList("index", null, "testDisable"));
- }
-
- @Override
- public void start() {
- for (IndexDefinition<?, ?, ?> def : defs) {
- start(def);
- }
- }
-
- private <K, V, I extends Index<K, V>> void start(
- IndexDefinition<K, V, I> def) {
- if (disabled.contains(def.getName())) {
- return;
- }
- Schema<V> schema;
- Integer v = singleVersions.get(def.getName());
- if (v == null) {
- schema = def.getLatest();
- } else {
- schema = def.getSchemas().get(v);
- if (schema == null) {
- throw new ProvisionException(String.format(
- "Unrecognized %s schema version: %s", def.getName(), v));
- }
- }
- I index = def.getIndexFactory().create(schema);
- def.getIndexCollection().setSearchIndex(index);
- def.getIndexCollection().addWriteIndex(index);
- }
-
- @Override
- public void stop() {
- // Do nothing; indexes are closed by IndexCollection.
- }
- }
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index b46f1f6..2f871fc 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.GerritIndexStatus;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.Index;
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 8852133..5f2ef43 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -47,7 +47,7 @@
':init-api',
':util',
'//gerrit-common:annotations',
- '//gerrit-lucene:lucene',
+ '//gerrit-index:index',
'//lib:args4j',
'//lib:derby',
'//lib:gwtjsonrpc',
@@ -66,6 +66,7 @@
REST_UTIL_DEPS = [
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-util-cli:cli',
'//lib:args4j',
'//lib:gwtorm',
@@ -120,6 +121,7 @@
':init-api',
':util',
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-gpg:gpg',
'//gerrit-lucene:lucene',
'//gerrit-oauth:oauth',
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index 5bdc8fb..8e3cbcf 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -45,6 +45,7 @@
':init-api',
':util',
'//gerrit-common:annotations',
+ '//gerrit-index:index',
'//gerrit-launcher:launcher', # We want this dep to be provided_deps
'//gerrit-lucene:lucene',
'//lib:args4j',
@@ -110,6 +111,7 @@
':init-api',
':util',
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-gpg:gpg',
'//gerrit-lucene:lucene',
'//gerrit-oauth:oauth',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index eb17530..9d4120c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -20,6 +20,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.EventBroker;
+import com.google.gerrit.elasticsearch.ElasticIndexModule;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.AllRequestFilter;
@@ -408,15 +409,18 @@
return cfgInjector.createChildInjector(modules);
}
- private AbstractModule createIndexModule() {
+ private Module createIndexModule() {
if (slave) {
return new DummyIndexModule();
}
+ if (luceneModule != null) {
+ return luceneModule;
+ }
switch (indexType) {
case LUCENE:
- return luceneModule != null
- ? luceneModule
- : LuceneIndexModule.latestVersionWithOnlineUpgrade();
+ return LuceneIndexModule.latestVersionWithOnlineUpgrade();
+ case ELASTICSEARCH:
+ return ElasticIndexModule.latestVersionWithOnlineUpgrade();
default:
throw new IllegalStateException("unsupported index.type = " + indexType);
}
@@ -426,6 +430,7 @@
indexType = IndexModule.getIndexType(cfgInjector);
switch (indexType) {
case LUCENE:
+ case ELASTICSEARCH:
break;
default:
throw new IllegalStateException("unsupported index.type = " + indexType);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 501b115..ee0d02f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.Die;
+import com.google.gerrit.elasticsearch.ElasticIndexModule;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
@@ -161,6 +162,10 @@
indexModule = LuceneIndexModule.singleVersionWithExplicitVersions(
versions, threads);
break;
+ case ELASTICSEARCH:
+ indexModule = ElasticIndexModule
+ .singleVersionWithExplicitVersions(versions, threads);
+ break;
default:
throw new IllegalStateException("unsupported index.type");
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index 185063b..c8d8edb 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -15,7 +15,8 @@
package com.google.gerrit.pgm.init;
import com.google.common.collect.Iterables;
-import com.google.gerrit.lucene.AbstractLuceneIndex;
+import com.google.common.collect.Sets;
+import com.google.gerrit.index.IndexUtils;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
@@ -61,9 +62,17 @@
type = index.select("Type", "type", type);
}
+ if (type == IndexType.ELASTICSEARCH) {
+ index.select("Transport protocol", "protocol", "http",
+ Sets.newHashSet("http", "https"));
+ index.string("Hostname", "hostname", "localhost");
+ index.string("Port", "port", "9200");
+ index.string("Index Name", "name", "gerrit");
+ }
+
if ((site.isNew || isEmptySite()) && type == IndexType.LUCENE) {
for (SchemaDefinitions<?> def : IndexModule.ALL_SCHEMA_DEFS) {
- AbstractLuceneIndex.setReady(
+ IndexUtils.setReady(
site, def.getName(), def.getLatest().getVersion(), true);
}
} else {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index f3a68a1..0f3d45c 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -149,6 +149,7 @@
}
int ce = nextNonDigit(ref, cs);
if (ref.substring(ce).equals(RefNames.META_SUFFIX)
+ || ref.substring(ce).equals(RefNames.ROBOT_COMMENTS_SUFFIX)
|| PatchSet.Id.fromRef(ref, ce) >= 0) {
return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index b2bd818..7629705 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -68,6 +68,9 @@
/** Suffix of a meta ref in the NoteDb. */
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 String fullName(String ref) {
@@ -92,6 +95,14 @@
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) {
StringBuilder r = new StringBuilder();
r.append(REFS_USERS);
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java
new file mode 100644
index 0000000..da9584d
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java
@@ -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();
+ }
+}
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 66fc545..a50df82 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -91,6 +91,7 @@
':server',
'//gerrit-common:server',
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-extension-api:api',
'//gerrit-gpg:gpg',
'//gerrit-lucene:lucene',
@@ -181,6 +182,7 @@
'//gerrit-server/src/main/prolog:common',
'//lib/antlr:java_runtime',
],
+ visibility = ['PUBLIC'],
)
java_test(
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index 6c7ab3e..3874fc9 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -169,6 +169,20 @@
['src/test/java/com/google/gerrit/server/query/**/*.java'],
)
+java_library(
+ name = 'query_tests_code',
+ srcs = QUERY_TESTS,
+ deps = TESTUTIL_DEPS + [
+ ':testutil',
+ '//gerrit-antlr:query_exception',
+ '//gerrit-antlr:query_parser',
+ '//gerrit-common:annotations',
+ '//gerrit-server/src/main/prolog:common',
+ '//lib/antlr:java_runtime',
+ ],
+ visibility = ['//visibility:public'],
+)
+
junit_tests(
name = 'query_tests',
srcs = QUERY_TESTS,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
index f15ff66..8197689 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
@@ -21,6 +21,7 @@
import com.google.common.base.Optional;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -34,6 +35,7 @@
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
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.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
@@ -156,9 +158,17 @@
}
notes.load();
- List<Comment> comments = new ArrayList<>();
- comments.addAll(notes.getComments().values());
- return sort(comments);
+ return sort(Lists.newArrayList(notes.getComments().values()));
+ }
+
+ 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)
@@ -221,6 +231,14 @@
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
* comparison against an ancestor is done, so there can't be any comments on
@@ -309,6 +327,13 @@
.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,
Iterable<Comment> comments) throws OrmException {
for (Comment c : comments) {
@@ -352,11 +377,11 @@
return sort(result);
}
- private static List<Comment> commentsOnPatchSet(
- Collection<Comment> allComments,
+ private static <T extends Comment> List<T> commentsOnPatchSet(
+ Collection<T> allComments,
PatchSet.Id psId) {
- List<Comment> result = new ArrayList<>(allComments.size());
- for (Comment c : allComments) {
+ List<T> result = new ArrayList<>(allComments.size());
+ for (T c : allComments) {
if (c.key.patchSetId == psId.get()) {
result.add(c);
}
@@ -400,7 +425,7 @@
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);
return comments;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 63d2ddb..149931d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -174,7 +174,7 @@
this.loader = loader;
this.byName = byUsername;
this.readFromGit =
- cfg.getBoolean("user", null, "readProjectWatchesFromGit", true);
+ cfg.getBoolean("user", null, "readProjectWatchesFromGit", false);
this.watchConfig = watchConfig;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
index 7cda472..3748e17 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
@@ -60,7 +60,7 @@
this.dbProvider = dbProvider;
this.self = self;
this.readFromGit =
- cfg.getBoolean("user", null, "readProjectWatchesFromGit", true);
+ cfg.getBoolean("user", null, "readProjectWatchesFromGit", false);
this.watchConfig = watchConfig;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index 871b1cd..aa32d27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -101,7 +101,7 @@
return read(accountId).getKey(seq);
}
- public AccountSshKey addKey(Account.Id accountId, String pub)
+ public synchronized AccountSshKey addKey(Account.Id accountId, String pub)
throws IOException, ConfigInvalidException, InvalidSshKeyException {
VersionedAuthorizedKeys authorizedKeys = read(accountId);
AccountSshKey key = authorizedKeys.addKey(pub);
@@ -109,7 +109,7 @@
return key;
}
- public void deleteKey(Account.Id accountId, int seq)
+ public synchronized void deleteKey(Account.Id accountId, int seq)
throws IOException, ConfigInvalidException {
VersionedAuthorizedKeys authorizedKeys = read(accountId);
if (authorizedKeys.deleteKey(seq)) {
@@ -117,7 +117,7 @@
}
}
- public void markKeyInvalid(Account.Id accountId, int seq)
+ public synchronized void markKeyInvalid(Account.Id accountId, int seq)
throws IOException, ConfigInvalidException {
VersionedAuthorizedKeys authorizedKeys = read(accountId);
if (authorizedKeys.markKeyInvalid(seq)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/WatchConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/WatchConfig.java
index c3d28ca..a3cd0c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/WatchConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/WatchConfig.java
@@ -121,7 +121,7 @@
}
}
- public void upsertProjectWatches(Account.Id accountId,
+ public synchronized void upsertProjectWatches(Account.Id accountId,
Map<ProjectWatchKey, Set<NotifyType>> newProjectWatches)
throws IOException, ConfigInvalidException {
WatchConfig watchConfig = read(accountId);
@@ -131,7 +131,7 @@
commit(watchConfig);
}
- public void deleteProjectWatches(Account.Id accountId,
+ public synchronized void deleteProjectWatches(Account.Id accountId,
Collection<ProjectWatchKey> projectWatchKeys)
throws IOException, ConfigInvalidException {
WatchConfig watchConfig = read(accountId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
index 228dad6..bc38df2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
@@ -24,6 +24,7 @@
factory(ChangeApiImpl.Factory.class);
factory(CommentApiImpl.Factory.class);
+ factory(RobotCommentApiImpl.Factory.class);
factory(DraftApiImpl.Factory.class);
factory(RevisionApiImpl.Factory.class);
factory(FileApiImpl.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index fed792b..4e847a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
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.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
@@ -33,6 +34,7 @@
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.FileInfo;
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.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
@@ -50,6 +52,7 @@
import com.google.gerrit.server.change.GetRevisionActions;
import com.google.gerrit.server.change.ListRevisionComments;
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.PostReview;
import com.google.gerrit.server.change.PreviewSubmit;
@@ -58,6 +61,7 @@
import com.google.gerrit.server.change.RebaseUtil;
import com.google.gerrit.server.change.Reviewed;
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.TestSubmitType;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -101,12 +105,15 @@
private final Mergeable mergeable;
private final FileApiImpl.Factory fileApi;
private final ListRevisionComments listComments;
+ private final ListRobotComments listRobotComments;
private final ListRevisionDrafts listDrafts;
private final CreateDraftComment createDraft;
private final DraftComments drafts;
private final DraftApiImpl.Factory draftFactory;
private final Comments comments;
private final CommentApiImpl.Factory commentFactory;
+ private final RobotComments robotComments;
+ private final RobotCommentApiImpl.Factory robotCommentFactory;
private final GetRevisionActions revisionActions;
private final TestSubmitType testSubmitType;
private final TestSubmitType.Get getSubmitType;
@@ -131,12 +138,15 @@
Mergeable mergeable,
FileApiImpl.Factory fileApi,
ListRevisionComments listComments,
+ ListRobotComments listRobotComments,
ListRevisionDrafts listDrafts,
CreateDraftComment createDraft,
DraftComments drafts,
DraftApiImpl.Factory draftFactory,
Comments comments,
CommentApiImpl.Factory commentFactory,
+ RobotComments robotComments,
+ RobotCommentApiImpl.Factory robotCommentFactory,
GetRevisionActions revisionActions,
TestSubmitType testSubmitType,
TestSubmitType.Get getSubmitType,
@@ -160,12 +170,15 @@
this.mergeable = mergeable;
this.fileApi = fileApi;
this.listComments = listComments;
+ this.robotComments = robotComments;
+ this.listRobotComments = listRobotComments;
this.listDrafts = listDrafts;
this.createDraft = createDraft;
this.drafts = drafts;
this.draftFactory = draftFactory;
this.comments = comments;
this.commentFactory = commentFactory;
+ this.robotCommentFactory = robotCommentFactory;
this.revisionActions = revisionActions;
this.testSubmitType = testSubmitType;
this.getSubmitType = getSubmitType;
@@ -353,6 +366,15 @@
}
@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
public List<CommentInfo> commentsAsList() throws RestApiException {
try {
return listComments.getComments(revision);
@@ -371,6 +393,15 @@
}
@Override
+ public List<RobotCommentInfo> robotCommentsAsList() throws RestApiException {
+ try {
+ return listRobotComments.getComments(revision);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve robot comments", e);
+ }
+ }
+
+ @Override
public List<CommentInfo> draftsAsList() throws RestApiException {
try {
return listDrafts.getComments(revision);
@@ -413,6 +444,16 @@
}
@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
public BinaryResult patch() throws RestApiException {
try {
return getPatch.apply(revision);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java
new file mode 100644
index 0000000..9169a4f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java
@@ -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);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
index 1e48717..be019fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
@@ -21,8 +21,10 @@
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.Side;
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.reviewdb.client.Comment;
+import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -55,100 +57,134 @@
return this;
}
- CommentInfo format(Comment c) throws OrmException {
- AccountLoader loader = null;
- if (fillAccounts) {
- loader = accountLoaderFactory.create(true);
- }
- CommentInfo commentInfo = toCommentInfo(c, loader);
- if (fillAccounts) {
- loader.fill();
- }
- return commentInfo;
+ public CommentFormatter newCommentFormatter() {
+ return new CommentFormatter();
}
- Map<String, List<CommentInfo>> format(Iterable<Comment> l)
- throws OrmException {
- Map<String, List<CommentInfo>> out = new TreeMap<>();
- AccountLoader accountLoader = fillAccounts
- ? accountLoaderFactory.create(true)
- : null;
+ public RobotCommentFormatter newRobotCommentFormatter() {
+ return new RobotCommentFormatter();
+ }
- for (Comment c : l) {
- CommentInfo o = toCommentInfo(c, accountLoader);
- List<CommentInfo> list = out.get(o.path);
- if (list == null) {
- list = new ArrayList<>();
- out.put(o.path, list);
+ private abstract class BaseCommentFormatter<F extends Comment,
+ T extends CommentInfo> {
+ public T format(F comment) throws OrmException {
+ AccountLoader loader =
+ fillAccounts ? accountLoaderFactory.create(true) : null;
+ T info = toInfo(comment, loader);
+ if (loader != null) {
+ loader.fill();
}
- o.path = null;
- list.add(o);
+ return info;
}
- for (List<CommentInfo> list : out.values()) {
- Collections.sort(list, COMMENT_INFO_ORDER);
+ public Map<String, List<T>> format(Iterable<F> comments)
+ 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) {
- accountLoader.fill();
+ public List<T> formatAsList(Iterable<F> comments) throws OrmException {
+ 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)
- throws OrmException {
- AccountLoader accountLoader = fillAccounts
- ? accountLoaderFactory.create(true)
- : null;
- List<CommentInfo> out = FluentIterable
- .from(l)
- .transform(c -> toCommentInfo(c, accountLoader))
- .toSortedList(COMMENT_INFO_ORDER);
-
- if (accountLoader != null) {
- accountLoader.fill();
- }
-
- return out;
- }
-
- private CommentInfo toCommentInfo(Comment c, AccountLoader loader) {
- CommentInfo r = new CommentInfo();
- if (fillPatchSet) {
- r.patchSet = c.key.patchSetId;
- }
- r.id = Url.encode(c.key.uuid);
- r.path = c.key.filename;
- if (c.side <= 0) {
- r.side = Side.PARENT;
- if (c.side < 0) {
- r.parent = -c.side;
+ protected void fillCommentInfo(Comment c, CommentInfo r,
+ AccountLoader loader) {
+ if (fillPatchSet) {
+ r.patchSet = c.key.patchSetId;
+ }
+ r.id = Url.encode(c.key.uuid);
+ 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;
+ }
+ 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());
}
}
- 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) {
- 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;
+ class CommentFormatter extends BaseCommentFormatter<Comment, CommentInfo> {
+ @Override
+ protected CommentInfo toInfo(Comment c, AccountLoader loader) {
+ CommentInfo ci = new CommentInfo();
+ fillCommentInfo(c, ci, loader);
+ return ci;
}
- return range;
+
+ 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() {
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
index 37af37f..7ca8478 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -90,8 +90,8 @@
Op op = new Op(rsrc.getPatchSet().getId(), in);
bu.addOp(rsrc.getChange().getId(), op);
bu.execute();
- return Response.created(
- commentJson.get().setFillAccounts(false).format(op.comment));
+ return Response.created(commentJson.get().setFillAccounts(false)
+ .newCommentFormatter().format(op.comment));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
index d87c7eb..d601737 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
@@ -33,6 +33,6 @@
@Override
public CommentInfo apply(CommentResource rsrc) throws OrmException {
- return commentJson.get().format(rsrc.getComment());
+ return commentJson.get().newCommentFormatter().format(rsrc.getComment());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java
index 22f90c9..a380ce3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java
@@ -33,6 +33,6 @@
@Override
public CommentInfo apply(DraftCommentResource rsrc) throws OrmException {
- return commentJson.get().format(rsrc.getComment());
+ return commentJson.get().newCommentFormatter().format(rsrc.getComment());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRobotComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRobotComment.java
new file mode 100644
index 0000000..c10cd2e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRobotComment.java
@@ -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());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeComments.java
index 40fa7c8..32b5ae8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeComments.java
@@ -53,6 +53,7 @@
return commentJson.get()
.setFillAccounts(true)
.setFillPatchSet(true)
+ .newCommentFormatter()
.format(commentsUtil.publishedByChange(db.get(), cd.notes()));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
index dbbd35d..6a3e237 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
@@ -59,6 +59,6 @@
return commentJson.get()
.setFillAccounts(false)
.setFillPatchSet(true)
- .format(drafts);
+ .newCommentFormatter().format(drafts);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRevisionDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRevisionDrafts.java
index 2c45219..21d427c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRevisionDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRevisionDrafts.java
@@ -57,13 +57,13 @@
throws OrmException {
return commentJson.get()
.setFillAccounts(includeAuthorInfo())
- .format(listComments(rsrc));
+ .newCommentFormatter().format(listComments(rsrc));
}
public List<CommentInfo> getComments(RevisionResource rsrc)
throws OrmException {
return commentJson.get()
.setFillAccounts(includeAuthorInfo())
- .formatAsList(listComments(rsrc));
+ .newCommentFormatter().formatAsList(listComments(rsrc));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRobotComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRobotComments.java
new file mode 100644
index 0000000..01ad9ee
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListRobotComments.java
@@ -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());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 9b284fa..a52920a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -21,6 +21,7 @@
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.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 com.google.gerrit.extensions.registration.DynamicMap;
@@ -37,11 +38,13 @@
bind(Reviewers.class);
bind(DraftComments.class);
bind(Comments.class);
+ bind(RobotComments.class);
bind(Files.class);
bind(Votes.class);
DynamicMap.mapOf(binder(), CHANGE_KIND);
DynamicMap.mapOf(binder(), COMMENT_KIND);
+ DynamicMap.mapOf(binder(), ROBOT_COMMENT_KIND);
DynamicMap.mapOf(binder(), DRAFT_COMMENT_KIND);
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), REVIEWER_KIND);
@@ -116,6 +119,9 @@
child(REVISION_KIND, "comments").to(Comments.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);
put(FILE_KIND, "reviewed").to(PutReviewed.class);
delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 1ac9e11..7ccda0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
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 com.google.auto.value.AutoValue;
@@ -43,10 +44,12 @@
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.DraftHandling;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.api.changes.ReviewResult;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.restapi.AuthException;
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.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -59,6 +62,7 @@
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
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.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -76,6 +80,7 @@
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.notedb.ChangeNotes;
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.project.ChangeControl;
import com.google.gerrit.server.query.change.ChangeData;
@@ -94,7 +99,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -118,6 +122,7 @@
private final CommentAdded commentAdded;
private final PostReviewers postReviewers;
private final String serverId;
+ private final NotesMigration migration;
@Inject
PostReview(Provider<ReviewDb> db,
@@ -133,7 +138,8 @@
EmailReviewComments.Factory email,
CommentAdded commentAdded,
PostReviewers postReviewers,
- @GerritServerId String serverId) {
+ @GerritServerId String serverId,
+ NotesMigration migration) {
this.db = db;
this.batchUpdateFactory = batchUpdateFactory;
this.changes = changes;
@@ -148,6 +154,7 @@
this.commentAdded = commentAdded;
this.postReviewers = postReviewers;
this.serverId = serverId;
+ this.migration = migration;
}
@Override
@@ -173,6 +180,12 @@
if (input.comments != null) {
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) {
log.warn("notify = null; assuming notify = NONE");
input.notify = NotifyHandling.NONE;
@@ -316,16 +329,16 @@
}
}
- private void checkComments(RevisionResource revision, Map<String, List<CommentInput>> in)
- throws BadRequestException, OrmException {
- Iterator<Map.Entry<String, List<CommentInput>>> mapItr =
- in.entrySet().iterator();
+ private <T extends CommentInput> void checkComments(RevisionResource revision,
+ Map<String, List<T>> in) throws BadRequestException, OrmException {
+ Iterator<? extends Map.Entry<String, List<T>>> mapItr =
+ in.entrySet().iterator();
Set<String> filePaths =
Sets.newHashSet(changeDataFactory.create(
db.get(), revision.getControl()).filePaths(
revision.getPatchSet()));
while (mapItr.hasNext()) {
- Map.Entry<String, List<CommentInput>> ent = mapItr.next();
+ Map.Entry<String, List<T>> ent = mapItr.next();
String path = ent.getKey();
if (!filePaths.contains(path) && !Patch.isMagic(path)) {
throw new BadRequestException(String.format(
@@ -333,7 +346,7 @@
path, revision.getChange().currentPatchSetId()));
}
if (Patch.isMagic(path)) {
- for (CommentInput comment : ent.getValue()) {
+ for (T comment : ent.getValue()) {
if (comment.side == Side.PARENT && comment.parent == null) {
throw new BadRequestException(
String.format("cannot comment on %s on auto-merge", path));
@@ -341,15 +354,15 @@
}
}
- List<CommentInput> list = ent.getValue();
+ List<T> list = ent.getValue();
if (list == null) {
mapItr.remove();
continue;
}
- Iterator<CommentInput> listItr = list.iterator();
+ Iterator<T> listItr = list.iterator();
while (listItr.hasNext()) {
- CommentInput c = listItr.next();
+ T c = listItr.next();
if (c == null) {
listItr.remove();
continue;
@@ -370,6 +383,25 @@
}
}
+ 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.
*/
@@ -427,6 +459,7 @@
ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
boolean dirty = false;
dirty |= insertComments(ctx);
+ dirty |= insertRobotComments(ctx);
dirty |= updateLabels(ctx);
dirty |= insertMessage(ctx);
return dirty;
@@ -471,7 +504,7 @@
Set<CommentSetEntry> existingIds = in.omitDuplicateComments
? readExistingComments(ctx)
- : Collections.<CommentSetEntry>emptySet();
+ : Collections.emptySet();
for (Map.Entry<String, List<CommentInput>> ent : map.entrySet()) {
String path = ent.getKey();
@@ -530,14 +563,53 @@
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)
throws OrmException {
- Set<CommentSetEntry> r = new HashSet<>();
- for (Comment c : commentsUtil.publishedByChange(ctx.getDb(),
- ctx.getNotes())) {
- r.add(CommentSetEntry.create(c));
- }
- return r;
+ return commentsUtil.publishedByChange(ctx.getDb(), ctx.getNotes())
+ .stream().map(CommentSetEntry::create).collect(toSet());
+ }
+
+ private Set<CommentSetEntry> readExistingRobotComments(ChangeContext ctx)
+ throws OrmException {
+ return commentsUtil.robotCommentsByChange(ctx.getNotes())
+ .stream().map(CommentSetEntry::create).collect(toSet());
}
private Map<String, Comment> changeDrafts(ChangeContext ctx)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index feb17b3..0808f95 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -92,8 +92,9 @@
Op op = new Op(rsrc.getComment().key, in);
bu.addOp(rsrc.getChange().getId(), op);
bu.execute();
- return Response.ok(
- commentJson.get().setFillAccounts(false).format(op.comment));
+ return Response.ok(commentJson.get()
+ .setFillAccounts(false)
+ .newCommentFormatter().format(op.comment));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotCommentResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotCommentResource.java
new file mode 100644
index 0000000..856c777
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotCommentResource.java
@@ -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();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotComments.java
new file mode 100644
index 0000000..886af1d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RobotComments.java
@@ -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);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 6a25862..9e0be86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -55,7 +55,7 @@
*/
public class IndexModule extends LifecycleModule {
public enum IndexType {
- LUCENE
+ LUCENE, ELASTICSEARCH
}
public static final ImmutableCollection<SchemaDefinitions<?>> ALL_SCHEMA_DEFS =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 93f820d..bce114f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.Patch;
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.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
@@ -165,6 +166,14 @@
private void appendComment(StringBuilder out, int contextLines,
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;
Comment.Range range = comment.range;
if (range != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 6d546cd..b4ab290 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -138,7 +138,7 @@
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, OrmException, IOException {
- RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
+ RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr);
Set<RevId> updatedRevs =
Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
@@ -158,7 +158,7 @@
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);
+ byte[] data = e.getValue().build(noteUtil, noteUtil.getWriteJson());
if (!Arrays.equals(data, e.getValue().baseRaw)) {
touchedAnyRevs = true;
}
@@ -189,8 +189,8 @@
return cb;
}
- private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
- throws ConfigInvalidException, OrmException, IOException {
+ private RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap(RevWalk rw,
+ ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
if (migration.readChanges()) {
// If reading from changes is enabled, then the old DraftCommentNotes
// already parsed the revision notes. We can reuse them as long as the ref
@@ -202,7 +202,8 @@
if (draftNotes != null) {
ObjectId idFromNotes =
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
- RevisionNoteMap rnm = draftNotes.getRevisionNoteMap();
+ RevisionNoteMap<ChangeRevisionNote> rnm =
+ draftNotes.getRevisionNoteMap();
if (idFromNotes.equals(curr) && rnm != null) {
return rnm;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 313d8b7..360785f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -45,6 +45,7 @@
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.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.ReviewerSet;
@@ -339,10 +340,11 @@
// Parsed note map state, used by ChangeUpdate to make in-place editing of
// notes easier.
- RevisionNoteMap revisionNoteMap;
+ RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
private NoteDbUpdateManager.Result rebuildResult;
private DraftCommentNotes draftCommentNotes;
+ private RobotCommentNotes robotCommentNotes;
@VisibleForTesting
public ChangeNotes(Args args, Change change) {
@@ -448,6 +450,12 @@
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
* not be reloaded. However, this method will load the comments if no draft
@@ -464,11 +472,22 @@
}
}
+ private void loadRobotComments() throws OrmException {
+ if (robotCommentNotes == null) {
+ robotCommentNotes = new RobotCommentNotes(args, change);
+ robotCommentNotes.load();
+ }
+ }
+
@VisibleForTesting
DraftCommentNotes getDraftCommentNotes() {
return draftCommentNotes;
}
+ RobotCommentNotes getRobotCommentNotes() {
+ return robotCommentNotes;
+ }
+
public boolean containsComment(Comment c) throws OrmException {
if (containsCommentPublished(c)) {
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index a8f85a4..85df4b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -73,14 +73,14 @@
* used as an optimization; {@link ChangeNotes} is capable of lazily loading
* it as necessary.
*/
- @Nullable abstract RevisionNoteMap revisionNoteMap();
+ @Nullable abstract RevisionNoteMap<ChangeRevisionNote> revisionNoteMap();
}
private class Loader implements Callable<ChangeNotesState> {
private final Key key;
private final ChangeNotesRevWalk rw;
- private RevisionNoteMap revisionNoteMap;
+ private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
private Loader(Key key, ChangeNotesRevWalk rw) {
this.key = key;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index a0e7f305..de37b72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -149,7 +149,7 @@
private String submissionId;
private String tag;
private PatchSet.Id currentPatchSetId;
- private RevisionNoteMap revisionNoteMap;
+ private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
ChangeNotesParser(Change.Id changeId, ObjectId tip, ChangeNotesRevWalk walk,
ChangeNoteUtil noteUtil, NoteDbMetrics metrics) {
@@ -195,7 +195,7 @@
return buildState();
}
- RevisionNoteMap getRevisionNoteMap() {
+ RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap() {
return revisionNoteMap;
}
@@ -630,18 +630,18 @@
revisionNoteMap = RevisionNoteMap.parse(
noteUtil, id, reader, NoteMap.read(reader, tipCommit),
PatchLineComment.Status.PUBLISHED);
- Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes;
+ Map<RevId, ChangeRevisionNote> rns = revisionNoteMap.revisionNotes;
- for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) {
- for (Comment c : e.getValue().comments) {
+ for (Map.Entry<RevId, ChangeRevisionNote> e : rns.entrySet()) {
+ for (Comment c : e.getValue().getComments()) {
comments.put(e.getKey(), c);
}
}
for (PatchSet ps : patchSets.values()) {
- RevisionNote rn = rns.get(ps.getRevision());
- if (rn != null && rn.pushCert != null) {
- ps.setPushCertificate(rn.pushCert);
+ ChangeRevisionNote rn = rns.get(ps.getRevision());
+ if (rn != null && rn.getPushCert() != null) {
+ ps.setPushCertificate(rn.getPushCert());
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
new file mode 100644
index 0000000..b95c92a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -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);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 263edab..96cd4c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -51,6 +51,7 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
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.account.AccountCache;
import com.google.gerrit.server.config.AnonymousCowardName;
@@ -110,6 +111,7 @@
private final AccountCache accountCache;
private final ChangeDraftUpdate.Factory draftUpdateFactory;
+ private final RobotCommentUpdate.Factory robotCommentUpdateFactory;
private final NoteDbUpdateManager.Factory updateManagerFactory;
private final Table<String, Account.Id, Optional<Short>> approvals;
@@ -135,6 +137,7 @@
private boolean isAllowWriteToNewtRef;
private ChangeDraftUpdate draftUpdate;
+ private RobotCommentUpdate robotCommentUpdate;
@AssistedInject
private ChangeUpdate(
@@ -144,11 +147,12 @@
AccountCache accountCache,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
+ RobotCommentUpdate.Factory robotCommentUpdateFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
ChangeNoteUtil noteUtil) {
this(serverIdent, anonymousCowardName, migration, accountCache,
- updateManagerFactory, draftUpdateFactory,
+ updateManagerFactory, draftUpdateFactory, robotCommentUpdateFactory,
projectCache, ctl, serverIdent.getWhen(), noteUtil);
}
@@ -160,13 +164,14 @@
AccountCache accountCache,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
+ RobotCommentUpdate.Factory robotCommentUpdateFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
@Assisted Date when,
ChangeNoteUtil noteUtil) {
this(serverIdent, anonymousCowardName, migration, accountCache,
- updateManagerFactory, draftUpdateFactory, ctl,
- when,
+ updateManagerFactory, draftUpdateFactory, robotCommentUpdateFactory,
+ ctl, when,
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
noteUtil);
}
@@ -188,6 +193,7 @@
AccountCache accountCache,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
+ RobotCommentUpdate.Factory robotCommentUpdateFactory,
@Assisted ChangeControl ctl,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator,
@@ -196,6 +202,7 @@
anonymousCowardName, noteUtil, when);
this.accountCache = accountCache;
this.draftUpdateFactory = draftUpdateFactory;
+ this.robotCommentUpdateFactory = robotCommentUpdateFactory;
this.updateManagerFactory = updateManagerFactory;
this.approvals = approvals(labelNameComparator);
}
@@ -208,6 +215,7 @@
AccountCache accountCache,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
+ RobotCommentUpdate.Factory robotCommentUpdateFactory,
ChangeNoteUtil noteUtil,
@Assisted Change change,
@Assisted @Nullable Account.Id accountId,
@@ -218,6 +226,7 @@
accountId, authorIdent, when);
this.accountCache = accountCache;
this.draftUpdateFactory = draftUpdateFactory;
+ this.robotCommentUpdateFactory = robotCommentUpdateFactory;
this.updateManagerFactory = updateManagerFactory;
this.approvals = approvals(labelNameComparator);
}
@@ -320,6 +329,12 @@
}
}
+ public void putRobotComment(RobotComment c) {
+ verifyComment(c);
+ createRobotCommentUpdateIfNull();
+ robotCommentUpdate.putComment(c);
+ }
+
public void deleteComment(Comment c) {
verifyComment(c);
createDraftUpdateIfNull().deleteComment(c);
@@ -340,6 +355,21 @@
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) {
checkArgument(c.revId != null, "RevId required for comment: %s", c);
checkArgument(c.author.getId().equals(getAccountId()),
@@ -415,7 +445,7 @@
if (comments.isEmpty() && pushCert == null) {
return null;
}
- RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
+ RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr);
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
for (Comment c : comments) {
@@ -431,15 +461,15 @@
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
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);
}
return rnm.noteMap.writeTree(inserter);
}
- private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
- throws ConfigInvalidException, OrmException, IOException {
+ private RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap(RevWalk rw,
+ ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
if (curr.equals(ObjectId.zeroId())) {
return RevisionNoteMap.emptyMap();
}
@@ -467,12 +497,12 @@
PatchLineComment.Status.PUBLISHED);
}
- private void checkComments(Map<RevId, RevisionNote> existingNotes,
+ private void checkComments(Map<RevId, ChangeRevisionNote> existingNotes,
Map<RevId, RevisionNoteBuilder> toUpdate) throws OrmException {
// Prohibit various kinds of illegal operations on comments.
Set<Comment.Key> existing = new HashSet<>();
- for (RevisionNote rn : existingNotes.values()) {
- for (Comment c : rn.comments) {
+ for (ChangeRevisionNote rn : existingNotes.values()) {
+ for (Comment c : rn.getComments()) {
existing.add(c.key);
if (draftUpdate != null) {
// Take advantage of an existing update on All-Users to prune any
@@ -677,6 +707,10 @@
return draftUpdate;
}
+ RobotCommentUpdate getRobotCommentUpdate() {
+ return robotCommentUpdate;
+ }
+
public void setAllowWriteToNewRef(boolean allow) {
isAllowWriteToNewtRef = allow;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 34da36d..661112e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -70,7 +70,7 @@
private final NoteDbUpdateManager.Result rebuildResult;
private ImmutableListMultimap<RevId, Comment> comments;
- private RevisionNoteMap revisionNoteMap;
+ private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
@AssistedInject
DraftCommentNotes(
@@ -103,7 +103,7 @@
this.rebuildResult = rebuildResult;
}
- RevisionNoteMap getRevisionNoteMap() {
+ RevisionNoteMap<ChangeRevisionNote> getRevisionNoteMap() {
return revisionNoteMap;
}
@@ -144,8 +144,8 @@
args.noteUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit),
PatchLineComment.Status.DRAFT);
Multimap<RevId, Comment> cs = ArrayListMultimap.create();
- for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) {
- for (Comment c : rn.comments) {
+ for (ChangeRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
+ for (Comment c : rn.getComments()) {
cs.put(new RevId(c.revId), c);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
index 876f47f..72a1f1b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -53,6 +53,8 @@
factory(ChangeUpdate.Factory.class);
factory(ChangeDraftUpdate.Factory.class);
factory(DraftCommentNotes.Factory.class);
+ factory(RobotCommentUpdate.Factory.class);
+ factory(RobotCommentNotes.Factory.class);
factory(NoteDbUpdateManager.Factory.class);
if (!useTestBindings) {
install(ChangeNotesCache.module());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 7f6eb5c..300f753 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -179,6 +179,7 @@
private final Project.NameKey projectName;
private final ListMultimap<String, ChangeUpdate> changeUpdates;
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
+ private final ListMultimap<String, RobotCommentUpdate> robotCommentUpdates;
private final Set<Change.Id> toDelete;
private OpenRepo changeRepo;
@@ -199,6 +200,7 @@
this.projectName = projectName;
changeUpdates = ArrayListMultimap.create();
draftUpdates = ArrayListMultimap.create();
+ robotCommentUpdates = ArrayListMultimap.create();
toDelete = new HashSet<>();
}
@@ -273,6 +275,7 @@
}
return changeUpdates.isEmpty()
&& draftUpdates.isEmpty()
+ && robotCommentUpdates.isEmpty()
&& toDelete.isEmpty();
}
@@ -294,6 +297,10 @@
if (du != null) {
draftUpdates.put(du.getRefName(), du);
}
+ RobotCommentUpdate rcu = update.getRobotCommentUpdate();
+ if (rcu != null) {
+ robotCommentUpdates.put(rcu.getRefName(), rcu);
+ }
}
public void add(ChangeDraftUpdate draftUpdate) {
@@ -453,6 +460,9 @@
if (!draftUpdates.isEmpty()) {
addUpdates(draftUpdates, allUsersRepo);
}
+ if (!robotCommentUpdates.isEmpty()) {
+ addUpdates(robotCommentUpdates, changeRepo);
+ }
for (Change.Id id : toDelete) {
doDelete(id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
index 7d44ffb..46e6dc5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -14,106 +14,66 @@
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 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.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 RevisionNote {
+abstract class RevisionNote<T extends Comment> {
static final int MAX_NOTE_SZ = 25 << 20;
- 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 static void trimLeadingEmptyLines(byte[] bytes, MutableInteger p) {
+ protected static void trimLeadingEmptyLines(byte[] bytes, MutableInteger p) {
while (p.value < bytes.length && bytes[p.value] == '\n') {
p.value++;
}
}
- 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);
+ private final ObjectReader reader;
+ private final ObjectId noteId;
+
+ private byte[] raw;
+ private ImmutableList<T> comments;
+
+ RevisionNote(ObjectReader reader, ObjectId noteId) {
+ this.reader = reader;
+ this.noteId = noteId;
}
- final byte[] raw;
- final ImmutableList<Comment> comments;
- final String pushCert;
+ public byte[] getRaw() {
+ checkParsed();
+ return raw;
+ }
- RevisionNote(ChangeNoteUtil noteUtil, Change.Id changeId,
- ObjectReader reader, ObjectId noteId, PatchLineComment.Status status)
- throws ConfigInvalidException, IOException {
+ public ImmutableList<T> getComments() {
+ checkParsed();
+ return comments;
+ }
+ public void parse() throws IOException, ConfigInvalidException {
raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
MutableInteger p = new MutableInteger();
trimLeadingEmptyLines(raw, p);
if (p.value >= raw.length) {
comments = null;
- pushCert = null;
return;
}
- if (isJson(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));
+ comments = ImmutableList.copyOf(parse(raw, p.value));
}
- private static boolean isJson(byte[] raw, int offset) {
- return raw[offset] == '{' || raw[offset] == '[';
- }
+ protected abstract List<T> parse(byte[] raw, int offset)
+ throws IOException, ConfigInvalidException;
- private RevisionNoteData parseJson(ChangeNoteUtil noteUtil, int offset)
- throws IOException{
- 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;
+ protected void checkParsed() {
+ checkState(raw != null, "revision note not parsed yet");
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
index 266a891..8eacb1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -37,10 +37,12 @@
class RevisionNoteBuilder {
static class Cache {
- private final RevisionNoteMap revisionNoteMap;
+ private final RevisionNoteMap<?
+ extends RevisionNote<? extends Comment>> revisionNoteMap;
private final Map<RevId, RevisionNoteBuilder> builders;
- Cache(RevisionNoteMap revisionNoteMap) {
+ Cache(RevisionNoteMap<?
+ extends RevisionNote<? extends Comment>> revisionNoteMap) {
this.revisionNoteMap = revisionNoteMap;
this.builders = new HashMap<>();
}
@@ -61,18 +63,20 @@
}
final byte[] baseRaw;
- final List<Comment> baseComments;
+ final List<? extends Comment> baseComments;
final Map<Comment.Key, Comment> put;
final Set<Comment.Key> delete;
private String pushCert;
- RevisionNoteBuilder(RevisionNote base) {
+ RevisionNoteBuilder(RevisionNote<? extends Comment> base) {
if (base != null) {
- baseRaw = base.raw;
- baseComments = base.comments;
- put = Maps.newHashMapWithExpectedSize(base.comments.size());
- pushCert = base.pushCert;
+ baseRaw = base.getRaw();
+ baseComments = base.getComments();
+ put = Maps.newHashMapWithExpectedSize(baseComments.size());
+ if (base instanceof ChangeRevisionNote) {
+ pushCert = ((ChangeRevisionNote) base).getPushCert();
+ }
} else {
baseRaw = new byte[0];
baseComments = Collections.emptyList();
@@ -82,9 +86,10 @@
delete = new HashSet<>();
}
- public byte[] build(ChangeNoteUtil noteUtil) throws IOException {
+ public byte[] build(ChangeNoteUtil noteUtil, boolean writeJson)
+ throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- if (noteUtil.getWriteJson()) {
+ if (writeJson) {
buildNoteJson(noteUtil, out);
} else {
buildNoteLegacy(noteUtil, out);
@@ -123,7 +128,7 @@
return all;
}
- private void buildNoteJson(final ChangeNoteUtil noteUtil, OutputStream out)
+ private void buildNoteJson(ChangeNoteUtil noteUtil, OutputStream out)
throws IOException {
Multimap<Integer, Comment> comments = buildCommentMap();
if (comments.isEmpty() && pushCert == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
index 1783c1a..8a9f711 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableMap;
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.RevId;
@@ -28,30 +29,45 @@
import java.util.HashMap;
import java.util.Map;
-class RevisionNoteMap {
+class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
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,
PatchLineComment.Status status)
- throws ConfigInvalidException, IOException {
- Map<RevId, RevisionNote> result = new HashMap<>();
+ throws ConfigInvalidException, IOException {
+ Map<RevId, ChangeRevisionNote> result = new HashMap<>();
for (Note note : noteMap) {
- RevisionNote rn = new RevisionNote(
+ ChangeRevisionNote rn = new ChangeRevisionNote(
noteUtil, changeId, reader, note.getData(), status);
+ rn.parse();
result.put(new RevId(note.name()), rn);
}
- return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
+ return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
}
- static RevisionNoteMap emptyMap() {
- return new RevisionNoteMap(NoteMap.newEmptyMap(),
- ImmutableMap.<RevId, RevisionNote> of());
+ static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
+ ChangeNoteUtil noteUtil, ObjectReader reader, NoteMap noteMap)
+ 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,
- ImmutableMap<RevId, RevisionNote> revisionNotes) {
+ ImmutableMap<RevId, T> revisionNotes) {
this.noteMap = noteMap;
this.revisionNotes = revisionNotes;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
new file mode 100644
index 0000000..4a26d7e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
@@ -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();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
new file mode 100644
index 0000000..fd47d02
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
@@ -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();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
new file mode 100644
index 0000000..e007ff3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
@@ -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;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java
new file mode 100644
index 0000000..ea3a149
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java
@@ -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;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
index 36e5792..e98211e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.query;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.IndexConfig;
@@ -74,6 +75,25 @@
}
}
+ /**
+ * Run multiple queries in parallel.
+ * <p>
+ * If a limit was specified using {@link #setLimit(int)}, that limit is
+ * applied to each query independently.
+ *
+ * @param queries list of queries.
+ * @return results of the queries, one list of results per input query, in the
+ * same order as the input.
+ */
+ public List<List<T>> query(List<Predicate<T>> queries) throws OrmException {
+ try {
+ return Lists.transform(
+ queryProcessor.query(queries), QueryResult::entities);
+ } catch (QueryParseException e) {
+ throw new OrmException(e);
+ }
+ }
+
protected Schema<T> schema() {
Index<?, T> index = indexes != null ? indexes.getSearchIndex() : null;
return index != null ? index.getSchema() : null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
index d08f05c..0997a40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
@@ -122,16 +122,12 @@
return query(ImmutableList.of(query)).get(0);
}
- /*
- * Perform multiple queries over a list of query strings.
- * <p>
- * If a limit was specified using {@link #setLimit(int)} this method may
- * return up to {@code limit + 1} results, allowing the caller to determine if
- * there are more than {@code limit} matches and suggest to its own caller
- * that the query could be retried with {@link #setStart(int)}.
+ /**
+ * Perform multiple queries in parallel.
*
- * @param queries the queries.
- * @return results of the queries, one list per input query.
+ * @param queries list of queries.
+ * @return results of the queries, one QueryResult per input query, in the
+ * same order as the input.
*/
public List<QueryResult<T>> query(List<Predicate<T>> queries)
throws OrmException, QueryParseException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
index 9573f99..3a0c699 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
@@ -176,7 +176,11 @@
}
stmt.executeBatch();
} catch (SQLException e) {
- throw convertError("insert", e);
+ OrmException ormException = convertError("insert", e);
+ if (ormException instanceof OrmDuplicateKeyException) {
+ return;
+ }
+ throw ormException;
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 84fd9d7..71401d7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -220,6 +220,9 @@
case LUCENE:
install(luceneIndexModule());
break;
+ case ELASTICSEARCH:
+ install(elasticIndexModule());
+ break;
default:
throw new ProvisionException(
"index type unsupported in tests: " + indexType);
@@ -242,14 +245,21 @@
}
private Module luceneIndexModule() {
+ return indexModule("com.google.gerrit.lucene.LuceneIndexModule");
+ }
+
+ private Module elasticIndexModule() {
+ return indexModule("com.google.gerrit.elasticsearch.ElasticIndexModule");
+ }
+
+ private Module indexModule(String moduleClassName) {
try {
Map<String, Integer> singleVersions = new HashMap<>();
int version = cfg.getInt("index", "lucene", "testVersion", -1);
if (version > 0) {
singleVersions.put(ChangeSchemaDefinitions.INSTANCE.getName(), version);
}
- Class<?> clazz =
- Class.forName("com.google.gerrit.lucene.LuceneIndexModule");
+ Class<?> clazz = Class.forName(moduleClassName);
Method m = clazz.getMethod(
"singleVersionWithExplicitVersions", Map.class, int.class);
return (Module) m.invoke(null, singleVersions, 0);
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 6d74a83..5dd1b04 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -5,6 +5,7 @@
srcs = glob(['src/main/java/**/*.java']),
deps = [
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-extension-api:api',
'//gerrit-gpg:gpg',
'//gerrit-httpd:httpd',
diff --git a/gerrit-war/BUILD b/gerrit-war/BUILD
index ae210d4..9262ad4 100644
--- a/gerrit-war/BUILD
+++ b/gerrit-war/BUILD
@@ -5,6 +5,7 @@
srcs = glob(['src/main/java/**/*.java']),
deps = [
'//gerrit-cache-h2:cache-h2',
+ '//gerrit-elasticsearch:elasticsearch',
'//gerrit-extension-api:api',
'//gerrit-gpg:gpg',
'//gerrit-httpd:httpd',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index fc0beae..9dba629 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -19,6 +19,7 @@
import com.google.common.base.Splitter;
import com.google.gerrit.common.EventBroker;
+import com.google.gerrit.elasticsearch.ElasticIndexModule;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
@@ -343,6 +344,8 @@
switch (indexType) {
case LUCENE:
return LuceneIndexModule.latestVersionWithOnlineUpgrade();
+ case ELASTICSEARCH:
+ return ElasticIndexModule.latestVersionWithOnlineUpgrade();
default:
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl
index fbf1e91..7a0e899 100644
--- a/lib/codemirror/cm.bzl
+++ b/lib/codemirror/cm.bzl
@@ -216,7 +216,7 @@
TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION
TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION
LICENSE = '//lib:LICENSE-codemirror-original'
-LICENSE_MINIFIED = '//lib:LICENSE-codemirror-original-minified'
+LICENSE_MINIFIED = '//lib:LICENSE-codemirror-minified'
DIFF_MATCH_PATCH_VERSION = '20121119-1'
DIFF_MATCH_PATCH_TOP = ('META-INF/resources/webjars/google-diff-match-patch/%s'
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 7c27477..55c07a6 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -47,6 +47,13 @@
)
maven_jar(
+ name = 'lang3',
+ id = 'org.apache.commons:commons-lang3:3.3.2',
+ sha1 = '90a3822c38ec8c996e84c16a3477ef632cbc87a3',
+ license = 'Apache2.0',
+)
+
+maven_jar(
name = 'net',
id = 'commons-net:commons-net:3.5',
sha1 = '342fc284019f590e1308056990fdb24a08f06318',
diff --git a/lib/commons/BUILD b/lib/commons/BUILD
index 7f6f6b2..d4d6145 100644
--- a/lib/commons/BUILD
+++ b/lib/commons/BUILD
@@ -1,3 +1,5 @@
+package(default_visibility = ['//visibility:public'])
+
java_library(
name = 'codec',
exports = ['@commons_codec//jar'],
@@ -27,6 +29,12 @@
)
java_library(
+ name = 'lang3',
+ exports = [ '@commons_lang3//jar'],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
name = 'net',
exports = ['@commons_net//jar'],
visibility = ['//visibility:public'],
diff --git a/lib/elasticsearch/BUCK b/lib/elasticsearch/BUCK
new file mode 100644
index 0000000..86594ce
--- /dev/null
+++ b/lib/elasticsearch/BUCK
@@ -0,0 +1,104 @@
+include_defs('//lib/maven.defs')
+
+# Java client library for Elasticsearch.
+maven_jar(
+ name = 'elasticsearch',
+ id = 'org.elasticsearch:elasticsearch:2.4.0',
+ sha1 = 'aeb9704a76fa8654c348f38fcbb993a952a7ab07',
+ attach_source = True,
+ repository = MAVEN_CENTRAL,
+ license = 'Apache2.0',
+ deps = [
+ ':jna',
+ ':hppc',
+ ':jsr166e',
+ ':netty',
+ ':t-digest',
+ ':compress-lzf',
+ '//lib/joda:joda-time',
+ '//lib/lucene:lucene-codecs',
+ '//lib/lucene:lucene-highlighter',
+ '//lib/lucene:lucene-join',
+ '//lib/lucene:lucene-memory',
+ '//lib/lucene:lucene-sandbox',
+ '//lib/lucene:lucene-suggest',
+ '//lib/lucene:lucene-queries',
+ '//lib/lucene:lucene-spatial',
+ '//lib/jackson:jackson-core',
+ '//lib/jackson:jackson-dataformat-cbor',
+ '//lib/jackson:jackson-dataformat-smile',
+ ]
+)
+
+# Java REST client for Elasticsearch.
+VERSION = '0.1.7'
+
+maven_jar(
+ name = 'jest-common',
+ id = 'io.searchbox:jest-common:' + VERSION,
+ sha1 = 'ff6e2694405557a3a02b444cb7f7da28c4d99f07',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'jest',
+ id = 'io.searchbox:jest:' + VERSION,
+ sha1 = '686619c7141edb50b562ad2a39d32ea4cf20b567',
+ license = 'Apache2.0',
+ deps = [
+ ':elasticsearch',
+ ':jest-common',
+ '//lib/commons:lang3',
+ '//lib/httpcomponents:httpasyncclient',
+ '//lib/httpcomponents:httpclient',
+ '//lib/httpcomponents:httpcore-nio',
+ '//lib/httpcomponents:httpcore-niossl',
+ ],
+)
+
+maven_jar(
+ name = 'compress-lzf',
+ id = 'com.ning:compress-lzf:1.0.2',
+ sha1 = '62896e6fca184c79cc01a14d143f3ae2b4f4b4ae',
+ license = 'Apache2.0',
+ visibility = ['//lib/elasticsearch:elasticsearch'],
+)
+
+maven_jar(
+ name = 'hppc',
+ id = 'com.carrotsearch:hppc:0.7.1',
+ sha1 = '8b5057f74ea378c0150a1860874a3ebdcb713767',
+ license = 'Apache2.0',
+ visibility = ['//lib/elasticsearch:elasticsearch'],
+)
+
+maven_jar(
+ name = 'jsr166e',
+ id = 'com.twitter:jsr166e:1.1.0',
+ sha1 = '233098147123ee5ddcd39ffc57ff648be4b7e5b2',
+ license = 'Apache2.0',
+ visibility = ['//lib/elasticsearch:elasticsearch'],
+)
+
+maven_jar(
+ name = 'netty',
+ id = 'io.netty:netty:3.10.0.Final',
+ sha1 = 'ad61cd1bba067e6634ddd3e160edf0727391ac30',
+ license = 'Apache2.0',
+ visibility = ['//lib/elasticsearch:elasticsearch'],
+)
+
+maven_jar(
+ name = 't-digest',
+ id = 'com.tdunning:t-digest:3.0',
+ sha1 = '84ccf145ac2215e6bfa63baa3101c0af41017cfc',
+ license = 'Apache2.0',
+ visibility = ['//lib/elasticsearch:elasticsearch'],
+)
+
+maven_jar(
+ name = 'jna',
+ id = 'net.java.dev.jna:jna:4.1.0',
+ sha1 = '1c12d070e602efd8021891cdd7fd18bc129372d4',
+ license = 'Apache2.0',
+)
diff --git a/lib/elasticsearch/BUILD b/lib/elasticsearch/BUILD
new file mode 100644
index 0000000..6c3d423
--- /dev/null
+++ b/lib/elasticsearch/BUILD
@@ -0,0 +1,92 @@
+package(default_visibility=['//visibility:public'])
+
+java_library(
+ name = 'elasticsearch',
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ exports = [ '@elasticsearch//jar' ],
+ runtime_deps = [
+ ':jna',
+ ':hppc',
+ ':jsr166e',
+ ':netty',
+ ':t-digest',
+ ':compress-lzf',
+ '//lib/joda:joda-time',
+ '//lib/lucene:lucene-codecs',
+ '//lib/lucene:lucene-highlighter',
+ '//lib/lucene:lucene-join',
+ '//lib/lucene:lucene-memory',
+ '//lib/lucene:lucene-sandbox',
+ '//lib/lucene:lucene-suggest',
+ '//lib/lucene:lucene-queries',
+ '//lib/lucene:lucene-spatial',
+ '//lib/jackson:jackson-core',
+ '//lib/jackson:jackson-dataformat-cbor',
+ '//lib/jackson:jackson-dataformat-smile',
+ ]
+)
+
+# Java REST client for Elasticsearch.
+VERSION = '0.1.7'
+
+java_library(
+ name = 'jest-common',
+ exports = [ '@jest_common//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+)
+
+java_library(
+ name = 'jest',
+ exports = [ '@jest//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ runtime_deps = [
+ ':elasticsearch',
+ ':jest-common',
+ '//lib/commons:lang3',
+ '//lib/httpcomponents:httpasyncclient',
+ '//lib/httpcomponents:httpclient',
+ '//lib/httpcomponents:httpcore-nio',
+ '//lib/httpcomponents:httpcore-niossl',
+ ],
+)
+
+java_library(
+ name = 'compress-lzf',
+ exports = [ '@compress_lzf//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ visibility = ['//lib/elasticsearch:__pkg__'],
+)
+
+java_library(
+ name = 'hppc',
+ exports = [ '@hppc//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ visibility = ['//lib/elasticsearch:__pkg__'],
+)
+
+java_library(
+ name = 'jsr166e',
+ exports = [ '@jsr166e//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ visibility = ['//lib/elasticsearch:__pkg__'],
+)
+
+java_library(
+ name = 'netty',
+ exports = [ '@netty//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ visibility = ['//lib/elasticsearch:__pkg__'],
+)
+
+java_library(
+ name = 't-digest',
+ exports = [ '@t_digest//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+ visibility = ['//lib/elasticsearch:__pkg__'],
+)
+
+java_library(
+ name = 'jna',
+ exports = [ '@jna//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+)
diff --git a/lib/httpcomponents/BUCK b/lib/httpcomponents/BUCK
index 03669f2..1e56f94 100644
--- a/lib/httpcomponents/BUCK
+++ b/lib/httpcomponents/BUCK
@@ -39,3 +39,25 @@
src_sha1 = '5394d3715181a87009032335a55b0a9789f6e26f',
license = 'Apache2.0',
)
+
+maven_jar(
+ name = 'httpasyncclient',
+ id = 'org.apache.httpcomponents:httpasyncclient:4.1.2',
+ sha1 = '95aa3e6fb520191a0970a73cf09f62948ee614be',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'httpcore-nio',
+ id = 'org.apache.httpcomponents:httpcore-nio:' + VERSION,
+ sha1 = 'a8c5e3c3bfea5ce23fb647c335897e415eb442e3',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'httpcore-niossl',
+ id = 'org.apache.httpcomponents:httpcore-niossl:4.0-alpha6',
+ sha1 = '9c662e7247ca8ceb1de5de629f685c9ef3e4ab58',
+ license = 'Apache2.0',
+ attach_source = False,
+)
diff --git a/lib/httpcomponents/BUILD b/lib/httpcomponents/BUILD
index 1dd3ccf..c11df29 100644
--- a/lib/httpcomponents/BUILD
+++ b/lib/httpcomponents/BUILD
@@ -1,3 +1,5 @@
+package(default_visibility = ['//visibility:public'])
+
java_library(
name = 'fluent-hc',
exports = ['@fluent_hc//jar'],
@@ -31,3 +33,21 @@
visibility = ['//visibility:public'],
data = ['//lib:LICENSE-Apache2.0'],
)
+
+java_library(
+ name = 'httpasyncclient',
+ exports = [ '@httpasyncclient//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'httpcore-nio',
+ exports = [ '@httpcore_nio//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'httpcore-niossl',
+ exports = ['@httpcore_niossl//jar'],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
diff --git a/lib/jackson/BUCK b/lib/jackson/BUCK
new file mode 100644
index 0000000..46056b5
--- /dev/null
+++ b/lib/jackson/BUCK
@@ -0,0 +1,26 @@
+include_defs('//lib/maven.defs')
+
+VERSION = '2.6.6'
+
+maven_jar(
+ name = 'jackson-core',
+ id = 'com.fasterxml.jackson.core:jackson-core:' + VERSION,
+ sha1 = '02eb801df67aacaf5b1deb4ac626e1964508e47b',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'jackson-dataformat-smile',
+ id = 'com.fasterxml.jackson.dataformat:jackson-dataformat-smile:' + VERSION,
+ sha1 = 'ccbfc948748ed2754a58c1af9e0a02b5cc1aed69',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'jackson-dataformat-cbor',
+ id = 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:' + VERSION,
+ sha1 = '34c7b7ff495fc6b049612bdc9db0900a68e112f8',
+ license = 'Apache2.0'
+)
+
+
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
new file mode 100644
index 0000000..87ea42e4
--- /dev/null
+++ b/lib/jackson/BUILD
@@ -0,0 +1,21 @@
+package(default_visibility = [ "//visibility:public"])
+
+VERSION = '2.6.6'
+
+java_library(
+ name = 'jackson-core',
+ exports = [ '@jackson_core//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+)
+
+java_library(
+ name = 'jackson-dataformat-smile',
+ exports = [ '@jackson_dataformat_smile//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+)
+
+java_library(
+ name = 'jackson-dataformat-cbor',
+ exports = [ '@jackson_dataformat_cbor//jar' ],
+ data = [ '//lib:LICENSE-Apache2.0' ],
+)
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index dee8ce8..8f2efa2 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -14,6 +14,17 @@
)
maven_jar(
+ name = 'lucene-codecs',
+ id = 'org.apache.lucene:lucene-codecs:' + VERSION,
+ sha1 = 'e01fe463d9490bb1b4a6a168e771f7b7255a50b1',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
name = 'lucene-core',
id = 'org.apache.lucene:lucene-core:' + VERSION,
sha1 = 'de5e5c3161ea01e89f2a09a14391f9b7ed66cdbb',
@@ -22,7 +33,7 @@
'META-INF/LICENSE.txt',
'META-INF/NOTICE.txt',
],
- visibility = [],
+ visibility = ['//gerrit-elasticsearch:elasticsearch'],
)
maven_jar(
@@ -51,6 +62,39 @@
)
maven_jar(
+ name = 'lucene-highlighter',
+ id = 'org.apache.lucene:lucene-highlighter:' + VERSION,
+ sha1 = 'd127ac514e9df965ab0b57d92bbe0c68d3d145b8',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
+ name = 'lucene-join',
+ id = 'org.apache.lucene:lucene-join:'+ VERSION,
+ sha1 = 'dac1b322508f3f2696ecc49a97311d34d8382054',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
+ name = 'lucene-memory',
+ id = 'org.apache.lucene:lucene-memory:' + VERSION,
+ sha1 = '7409db9863d8fbc265c27793c6cc7511304182c2',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
name = 'lucene-misc',
id = 'org.apache.lucene:lucene-misc:' + VERSION,
sha1 = '37bbe5a2fb429499dfbe75d750d1778881fff45d',
@@ -63,6 +107,49 @@
)
maven_jar(
+ name = 'lucene-sandbox',
+ id = 'org.apache.lucene:lucene-sandbox:' + VERSION,
+ sha1 = '30a91f120706ba66732d5a974b56c6971b3c8a16',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
+ name = 'lucene-spatial',
+ id = 'org.apache.lucene:lucene-spatial:' + VERSION,
+ sha1 = '8ed7a9a43d78222038573dd1c295a61f3c0bb0db',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+maven_jar(
+ name = 'lucene-suggest',
+ id = 'org.apache.lucene:lucene-suggest:' + VERSION,
+ sha1 = 'e8316b37dddcf2092a54dab2ce6aad0d5ad78585',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
+ name = 'lucene-queries',
+ id = 'org.apache.lucene:lucene-queries:' + VERSION,
+ sha1 = '692f1ad887cf4e006a23f45019e6de30f3312d3f',
+ license = 'Apache2.0',
+ exclude = [
+ 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE.txt',
+ ],
+)
+
+maven_jar(
name = 'lucene-queryparser',
id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
sha1 = '8ac921563e744463605284c6d9d2d95e1be5b87c',
@@ -73,3 +160,4 @@
'META-INF/NOTICE.txt',
],
)
+
diff --git a/lib/lucene/BUILD b/lib/lucene/BUILD
index e228c45..4739981 100644
--- a/lib/lucene/BUILD
+++ b/lib/lucene/BUILD
@@ -1,3 +1,4 @@
+package(default_visibility = [ "//visibility:public"])
load('//tools/bzl:maven.bzl', 'merge_maven_jars')
# core and backward-codecs both provide
@@ -21,6 +22,20 @@
)
java_library(
+ name = 'lucene-codecs',
+ exports = ['@lucene_codecs//jar'],
+ visibility = ['//visibility:public'],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-core',
+ exports = ['@lucene_core//jar'],
+ visibility = ['//visibility:public'],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
name = 'lucene-misc',
exports = ['@lucene_misc//jar'],
runtime_deps = [':lucene-core-and-backward-codecs'],
@@ -35,3 +50,45 @@
visibility = ['//visibility:public'],
data = ['//lib:LICENSE-Apache2.0'],
)
+
+java_library(
+ name = 'lucene-highlighter',
+ exports = [ '@lucene_highlighter//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-join',
+ exports = [ '@lucene_join//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-memory',
+ exports = [ '@lucene_memory//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-sandbox',
+ exports = [ '@lucene_sandbox//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-spatial',
+ exports = [ '@lucene_spatial//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-suggest',
+ exports = [ '@lucene_suggest//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
+ name = 'lucene-queries',
+ exports = [ '@lucene_queries//jar' ],
+ data = ['//lib:LICENSE-Apache2.0'],
+)
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index a7d99a7..f58f762 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -15,6 +15,7 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<dom-module id="gr-comment-list">
<template>
@@ -39,8 +40,6 @@
}
.message {
flex: 1;
- white-space: pre-wrap;
- word-wrap: break-word;
}
</style>
<template is="dom-repeat" items="[[_computeFilesFromComments(comments)]]" as="file">
@@ -60,7 +59,10 @@
File comment:
</span>
</a>
- <div class="message">[[comment.message]]</div>
+ <gr-linked-text class="message"
+ pre
+ content="[[comment.message]]"
+ config="[[projectConfig.commentlinks]]"></gr-linked-text>
</div>
</template>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index eaafc447..0362089 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -21,6 +21,7 @@
changeNum: Number,
comments: Object,
patchNum: Number,
+ projectConfig: Object,
},
_computeFilesFromComments: function(comments) {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index c0773d3..287e9e4 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -121,7 +121,8 @@
<gr-comment-list
comments="[[comments]]"
change-num="[[changeNum]]"
- patch-num="[[message._revision_number]]"></gr-comment-list>
+ patch-num="[[message._revision_number]]"
+ project-config="[[projectConfig]]"></gr-comment-list>
</div>
<a class="date" href$="[[_computeMessageHash(message)]]" on-tap="_handleLinkTap">
<gr-date-formatter date-str="[[message.date]]"></gr-date-formatter>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index efe8684..6c73e08 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -174,6 +174,10 @@
this._patchRange.patchNum, this._path, reviewed);
},
+ _checkForModifiers: function(e) {
+ return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
+ },
+
_handleKey: function(e) {
if (this.shouldSupressKeyboardShortcut(e)) { return; }
@@ -201,6 +205,7 @@
this.$.cursor.moveUp();
break;
case 67: // 'c'
+ if (this._checkForModifiers(e)) { return; }
if (!this.$.diff.isRangeSelected()) {
e.preventDefault();
var line = this.$.cursor.getTargetLineElement();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 14fd2b7..1eb3f95 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -509,5 +509,13 @@
assert.equal(element.$.cursor.initialLineNumber, 345);
assert.equal(element.$.cursor.side, 'left');
});
+
+ test('_checkForModifiers', function() {
+ assert.isTrue(element._checkForModifiers({altKey: true}));
+ assert.isTrue(element._checkForModifiers({ctrlKey: true}));
+ assert.isTrue(element._checkForModifiers({metaKey: true}));
+ assert.isTrue(element._checkForModifiers({shiftKey: true}));
+ assert.isFalse(element._checkForModifiers({}));
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index d565a12..fbd1ef3 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -74,9 +74,14 @@
return rect;
},
+ _checkForModifiers: function(e) {
+ return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
+ },
+
_handleKey: function(e) {
if (this.shouldSupressKeyboardShortcut(e)) { return; }
if (e.keyCode === 67) { // 'c'
+ if (this._checkForModifiers(e)) { return; }
e.preventDefault();
this._fireCreateComment();
}
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index adc8532..c12966d 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -117,5 +117,13 @@
document.createRange.restore();
});
});
+
+ test('_checkForModifiers', function() {
+ assert.isTrue(element._checkForModifiers({altKey: true}));
+ assert.isTrue(element._checkForModifiers({ctrlKey: true}));
+ assert.isTrue(element._checkForModifiers({metaKey: true}));
+ assert.isTrue(element._checkForModifiers({shiftKey: true}));
+ assert.isFalse(element._checkForModifiers({}));
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index 4f1cb87..8194c21 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -165,10 +165,9 @@
<select
is="gr-select"
bind-value="{{_localPrefs.email_strategy}}">
- <option value="ENABLED">Enabled</option>
- <option
- value="CC_ON_OWN_COMMENTS">CC Me On Comments I Write</option>
- <option value="DISABLED">Disabled</option>
+ <option value="CC_ON_OWN_COMMENTS">Every Comment</option>
+ <option value="ENABLED">Only Comments Left By Others</option>
+ <option value="DISABLED">None</option>
</select>
</span>
</section>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
index f380dd9..f6117e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -21,7 +21,7 @@
var TimeFormats = {
TIME_12: 'h:mm A', // 2:14 PM
- TIME_24: 'H:mm', // 14:14
+ TIME_24: 'HH:mm', // 14:14
MONTH_DAY: 'MMM DD', // Aug 29
MONTH_DAY_YEAR: 'MMM DD, YYYY', // Aug 29, 1997
};
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index d1886e7..8d65bc3 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -98,13 +98,13 @@
test('More than 24 hours but less than six months', function(done) {
testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
- 'Jun 15', 'Jun 15, 2015, 3:25', done);
+ 'Jun 15', 'Jun 15, 2015, 03:25', done);
});
test('More than six months', function(done) {
testDates('2015-09-15 20:34:00.000000000',
'2015-01-15 03:25:00.000000000',
- 'Jan 15, 2015', 'Jan 15, 2015, 3:25', done);
+ 'Jan 15, 2015', 'Jan 15, 2015, 03:25', done);
});
});
@@ -174,7 +174,7 @@
});
test('Default preferences are respected', function() {
- assert.equal(element._timeFormat, 'H:mm');
+ assert.equal(element._timeFormat, 'HH:mm');
assert.isFalse(element._relative);
});
});