Merge "Remove polyfilled image API"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 67eac4c..c029031 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2521,9 +2521,12 @@
of an Apache HTTP proxy layer as security enforcement on top of Gerrit
by returning a trusted username as HTTP Header.
+
+Allow multiple values to install multiple servlet filters.
++
Example of using a security library secure.jar under $GERRIT_SITE/lib
-that provides a org.anyorg.MySecureFilter Servlet Filter that enforces
-a trusted username in the `TRUSTED_USER` HTTP Header:
+that provides a org.anyorg.MySecureHeaderFilter Servlet Filter that enforces
+a trusted username in the `TRUSTED_USER` HTTP Header and
+org.anyorg.MySecureIPFilter that performs source IP security filtering:
----
[auth]
@@ -2531,9 +2534,25 @@
httpHeader = TRUSTED_USER
[httpd]
- filterClass = org.anyorg.MySecureFilter
+ filterClass = org.anyorg.MySecureHeaderFilter
+ filterClass = org.anyorg.MySecureIPFilter
----
+[[httpd.idleTimeout]]httpd.idleTimeout::
++
+Maximum idle time for a connection, which roughly translates to the
+TCP socket `SO_TIMEOUT`.
++
+The max idle time is applied:
+* When waiting for a new message to be received on a connection
+* When waiting for a new message to be sent on a connection
++
+This value is interpreted as the maximum time between some progress
+being made on the connection. So if a single byte is read or written,
+then the timeout is reset.
++
+By default, 30 seconds.
+
[[httpd.robotsFile]]httpd.robotsFile::
+
Location of an external robots.txt file to be used instead of the one
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 64b1c0d..7da501a 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -398,6 +398,10 @@
+
Update of the account secondary index
+* `com.google.gerrit.server.extensions.events.GroupIndexedListener`:
++
+Update of the group secondary index
+
* `com.google.gerrit.httpd.WebLoginListener`:
+
User login or logout interactively on the Web user interface.
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 5dcd947..2c42d74 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -523,6 +523,18 @@
In that case, care should be taken to prevent the CI system from
exposing secret details.
+[[ignore]]
+== Ignoring and Muting Changes
+
+Changes can be ignored, which means they will not appear in the 'Incoming
+Reviews' dashboard and any related email notifications will be suppressed.
+This can be useful when you are added as a reviewer to a change on which
+you do not actively participate in the review, but do not want to completely
+remove yourself.
+
+Alternatively, rather than completely ignoring the change, it can be muted.
+Muting a change means it will always be marked as "reviewed" in dashboards,
+until a new patch set is uploaded.
[[drafts]]
== Working with Drafts
@@ -536,12 +548,15 @@
changes can also be used to backup unfinished changes.
A draft change is created by pushing to the magic
-`refs/drafts/<target-branch>` ref.
+`refs/drafts/<target-branch>` ref, or by pushing with the 'draft'
+option to `refs/for/<target-branch>%draft`.
.Push a Draft Change
----
$ git commit
$ git push origin HEAD:refs/drafts/master
+ # or
+ $ git push origin HEAD:refs/for/master%draft
----
Draft changes have the state link:user-review-ui.html#draft[Draft] and
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index da30264..b7c50f4 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2166,14 +2166,23 @@
[[mark-private]]
=== Mark Private
--
-'PUT /changes/link:#change-id[\{change-id\}]/private'
+'POST /changes/link:#change-id[\{change-id\}]/private'
--
-Marks the change to be private. Note users can only mark own changes as private.
+Marks the change to be private. Changes may only be marked private by the
+owner or site administrators.
+
+A message can be specified in the request body inside a
+link:#private-input[PrivateInput] entity.
.Request
----
- PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/private HTTP/1.0
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/private HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message": "After this security fix has been released we can make it public now."
+ }
----
.Response
@@ -2192,9 +2201,17 @@
Marks the change to be non-private. Note users can only unmark own private
changes.
+A message can be specified in the request body inside a
+link:#private-input[PrivateInput] entity.
+
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/private HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message": "This is a security fix that must not be public."
+ }
----
.Response
@@ -2204,6 +2221,20 @@
If the change was already not private, the response is "`409 Conflict`".
+Please note that some proxies prohibit request bodies for DELETE
+requests. In this case, if you want to set a message options, use a
+POST request:
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/private.delete HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message": "This is a security fix that must not be public."
+ }
+----
+
[[ignore]]
=== Ignore
--
@@ -4394,6 +4425,62 @@
}
----
+[[delete-comment]]
+=== Delete Comment
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/link:#comment-id[\{comment-id\}]' +
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/link:#comment-id[\{comment-id\}]/delete'
+--
+
+Deletes a published comment of a revision. Instead of deleting the
+whole comment, this endpoint just replaces the comment's message
+with a new message, which contains the name of the user who deletes
+the comment and the reason why it's deleted. The reason can be
+provided in the request body as a
+link:#delete-comment-input[DeleteCommentInput] entity.
+
+Note that only users with the
+link:access-control.html#capability_administrateServer[Administrate Server]
+global capability are permitted to delete a comment.
+
+Please note that some proxies prohibit request bodies for DELETE
+requests. In this case, if you want to specify options, use a
+POST request:
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/comments/TvcXrmjM/delete HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "reason": "contains confidential information"
+ }
+----
+
+As response a link:#comment-info[CommentInfo] entity is returned that
+describes the updated comment.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "TvcXrmjM",
+ "path": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+ "line": 23,
+ "message": "Comment removed by: Administrator; Reason: contains confidential information",
+ "updated": "2013-02-26 15:40:43.986000000",
+ "author": {
+ "_account_id": 1000096,
+ "name": "John Doe",
+ "email": "john.doe@example.com"
+ }
+ }
+----
+
[[list-robot-comments]]
=== List Robot Comments
--
@@ -5739,6 +5826,20 @@
link:#web-link-info[WebLinkInfo] entities.
|===========================
+[[delete-comment-input]]
+=== DeleteCommentInput
+The `DeleteCommentInput` entity contains the option for deleting a comment.
+
+[options="header",cols="1,^1,5"]
+|=============================
+|Field Name ||Description
+|`reason` |optional|
+The reason why the comment should be deleted. +
+If set, the comment's message will be replaced with
+"Comment removed by: `name`; Reason: `reason`",
+or just "Comment removed by: `name`." if not set.
+|=============================
+
[[delete-reviewer-input]]
=== DeleteReviewerInput
The `DeleteReviewerInput` entity contains options for the deletion of a
@@ -6221,6 +6322,17 @@
identify the accounts that should be should be notified.
|=======================
+[[private-input]]
+=== PrivateInput
+The `PrivateInput` entity contains information for changing the private
+flag on a change.
+
+[options="header",cols="1,^1,5"]
+|=======================
+|Field Name||Description
+|`message` |optional|Message describing why the private flag was changed.
+|=======================
+
[[problem-info]]
=== ProblemInfo
The `ProblemInfo` entity contains a description of a potential consistency problem
diff --git a/WORKSPACE b/WORKSPACE
index 3b46d22..8cb061e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -6,9 +6,9 @@
http_archive(
name = "io_bazel_rules_closure",
- strip_prefix = "rules_closure-0.4.1",
- sha256 = "ba5e2e10cdc4027702f96e9bdc536c6595decafa94847d08ae28c6cb48225124",
- url = "http://bazel-mirror.storage.googleapis.com/github.com/bazelbuild/rules_closure/archive/0.4.1.tar.gz",
+ sha256 = "af1f5a31b8306faed9d09a38c8e2c1d6afc4c4a2dada3b5de11cceae8c7f4596",
+ strip_prefix = "rules_closure-f68d4b5a55c04ee50a3196590dce1ca8e7dbf438",
+ url = "https://bazel-mirror.storage.googleapis.com/github.com/bazelbuild/rules_closure/archive/f68d4b5a55c04ee50a3196590dce1ca8e7dbf438.tar.gz", # 2017-05-05
)
# File is specific to Polymer and copied from the Closure Github -- should be
@@ -24,18 +24,9 @@
# Prevent redundant loading of dependencies.
closure_repositories(
- omit_aopalliance=True,
- omit_args4j=True,
- omit_jsr305=True,
- omit_gson=True,
- omit_guava=True,
- omit_guice=True,
- omit_soy=True,
- omit_icu4j=True,
- omit_asm=True,
- omit_asm_analysis=True,
- omit_asm_commons=True,
- omit_asm_util=True,
+ omit_aopalliance = True,
+ omit_args4j = True,
+ omit_javax_inject = True,
)
ANTLR_VERS = "3.5.2"
@@ -109,18 +100,18 @@
sha1 = "5d9e2e895e3111622720157d0aa540066d5fce3a",
)
-GWT_VERS = "2.8.0"
+GWT_VERS = "2.8.1"
maven_jar(
name = "user",
artifact = "com.google.gwt:gwt-user:" + GWT_VERS,
- sha1 = "518579870499e15531f454f35dca0772d7fa31f7",
+ sha1 = "9a13fbee70848f1f1cddd3ae33ad180af3392d9e",
)
maven_jar(
name = "dev",
artifact = "com.google.gwt:gwt-dev:" + GWT_VERS,
- sha1 = "f160a61272c5ebe805cd2d3d3256ed3ecf14893f",
+ sha1 = "c7e88c07e9cda90cc623b4451d0d9713ae03aa53",
)
maven_jar(
@@ -203,8 +194,8 @@
maven_jar(
name = "joda_time",
- artifact = "joda-time:joda-time:2.9.8",
- sha1 = "03986e1763e5df02ad7fc040ecb555193a8436bb",
+ artifact = "joda-time:joda-time:2.9.9",
+ sha1 = "f7b520c458572890807d143670c9b24f4de90897",
)
maven_jar(
@@ -668,6 +659,9 @@
sha1 = "fd369423346b2f1525c413e33f8cf95b09c92cbd",
)
+# Note that all of the following org.apache.httpcomponents have newer versions,
+# but 4.4.1 is the only version that is available for all of them.
+# TODO: Check what combination of new versions are compatible.
HTTPCOMP_VERS = "4.4.1"
maven_jar(
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index 711b8cf..d0f998c 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -109,7 +109,7 @@
if (email != null) {
extIds.add(ExternalId.createEmail(id, email));
}
- externalIdsUpdate.create().insert(db, extIds);
+ externalIdsUpdate.create().insert(extIds);
Account a = new Account(id, TimeUtil.nowTs());
a.setFullName(fullName);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index a3ca832..ec4ff52 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -168,9 +168,9 @@
// savedExternalIds is null when we don't run SSH tests and the assume in
// @Before in AbstractDaemonTest prevents this class' @Before method from
// being executed.
- externalIdsUpdate.delete(db, getExternalIds(admin));
- externalIdsUpdate.delete(db, getExternalIds(user));
- externalIdsUpdate.insert(db, savedExternalIds);
+ externalIdsUpdate.delete(getExternalIds(admin));
+ externalIdsUpdate.delete(getExternalIds(user));
+ externalIdsUpdate.insert(savedExternalIds);
}
accountCache.evict(admin.getId());
accountCache.evict(user.getId());
@@ -533,7 +533,7 @@
ImmutableList.of(
ExternalId.createWithEmail(ExternalId.Key.parse(extId1), admin.id, email),
ExternalId.createWithEmail(ExternalId.Key.parse(extId2), admin.id, email));
- externalIdsUpdateFactory.create().insert(db, extIds);
+ externalIdsUpdateFactory.create().insert(extIds);
accountCache.evict(admin.id);
accountIndexedCounter.assertReindexOf(admin);
assertThat(
@@ -588,7 +588,7 @@
String email = "foo.bar@example.com";
externalIdsUpdateFactory
.create()
- .insert(db, ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id, email));
+ .insert(ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id, email));
accountCache.evict(admin.id);
assertEmail(byEmailCache.get(email), admin);
@@ -830,7 +830,7 @@
public void addOtherUsersGpgKey_Conflict() throws Exception {
// Both users have a matching external ID for this key.
addExternalIdEmail(admin, "test5@example.com");
- externalIdsUpdate.insert(db, ExternalId.create("foo", "myId", user.getId()));
+ externalIdsUpdate.insert(ExternalId.create("foo", "myId", user.getId()));
accountCache.evict(user.getId());
accountIndexedCounter.assertReindexOf(user);
@@ -1001,9 +1001,9 @@
return new String(out.toByteArray(), UTF_8);
}
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static void assertIteratorSize(int size, Iterator it) {
- assertThat(ImmutableList.copyOf(it)).hasSize(size);
+ private static void assertIteratorSize(int size, Iterator<?> it) {
+ List<?> lst = ImmutableList.copyOf(it);
+ assertThat(lst).hasSize(size);
}
private static void assertKeyMapContains(TestKey expected, Map<String, GpgKeyInfo> actualMap) {
@@ -1043,7 +1043,7 @@
expected.transform(k -> BaseEncoding.base16().encode(k.getPublicKey().getFingerprint()));
Iterable<String> actualFps =
externalIds
- .byAccount(db, currAccountId, SCHEME_GPGKEY)
+ .byAccount(currAccountId, SCHEME_GPGKEY)
.stream()
.map(e -> e.key().id())
.collect(toSet());
@@ -1072,7 +1072,7 @@
private void addExternalIdEmail(TestAccount account, String email) throws Exception {
checkNotNull(email);
externalIdsUpdate.insert(
- db, ExternalId.createWithEmail(name("test"), email, account.getId(), email));
+ ExternalId.createWithEmail(name("test"), email, account.getId(), email));
// Clear saved AccountState and ExternalIds.
accountCache.evict(account.getId());
accountIndexedCounter.assertReindexOf(account);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index a4a3c04..5d0f0ba 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -191,29 +191,55 @@
String changeId = result.getChangeId();
assertThat(gApi.changes().id(changeId).get().isPrivate).isNull();
- gApi.changes().id(changeId).setPrivate(true);
+ gApi.changes().id(changeId).setPrivate(true, null);
ChangeInfo info = gApi.changes().id(changeId).get();
assertThat(info.isPrivate).isTrue();
assertThat(Iterables.getLast(info.messages).message).isEqualTo("Set private");
assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_PRIVATE);
- gApi.changes().id(changeId).setPrivate(false);
+ gApi.changes().id(changeId).setPrivate(false, null);
info = gApi.changes().id(changeId).get();
assertThat(info.isPrivate).isNull();
assertThat(Iterables.getLast(info.messages).message).isEqualTo("Unset private");
assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_UNSET_PRIVATE);
+
+ String msg = "This is a security fix that must not be public.";
+ gApi.changes().id(changeId).setPrivate(true, msg);
+ info = gApi.changes().id(changeId).get();
+ assertThat(info.isPrivate).isTrue();
+ assertThat(Iterables.getLast(info.messages).message).isEqualTo("Set private\n\n" + msg);
+ assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_PRIVATE);
+
+ msg = "After this security fix has been released we can make it public now.";
+ gApi.changes().id(changeId).setPrivate(false, msg);
+ info = gApi.changes().id(changeId).get();
+ assertThat(info.isPrivate).isNull();
+ assertThat(Iterables.getLast(info.messages).message).isEqualTo("Unset private\n\n" + msg);
+ assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_UNSET_PRIVATE);
}
@Test
- public void setPrivateByOtherUser() throws Exception {
+ public void administratorCanSetUserChangePrivate() throws Exception {
TestRepository<InMemoryRepository> userRepo = cloneProject(project, user);
PushOneCommit.Result result =
pushFactory.create(db, user.getIdent(), userRepo).to("refs/for/master");
- assertThat(gApi.changes().id(result.getChangeId()).get().isPrivate).isNull();
+ String changeId = result.getChangeId();
+ assertThat(gApi.changes().id(changeId).get().isPrivate).isNull();
+
+ gApi.changes().id(changeId).setPrivate(true, null);
+ setApiUser(user);
+ ChangeInfo info = gApi.changes().id(changeId).get();
+ assertThat(info.isPrivate).isTrue();
+ }
+
+ @Test
+ public void cannotSetOtherUsersChangePrivate() throws Exception {
+ PushOneCommit.Result result = createChange();
+ setApiUser(user);
exception.expect(AuthException.class);
exception.expectMessage("not allowed to mark private");
- gApi.changes().id(result.getChangeId()).setPrivate(true);
+ gApi.changes().id(result.getChangeId()).setPrivate(true, null);
}
@Test
@@ -223,7 +249,7 @@
pushFactory.create(db, user.getIdent(), userRepo).to("refs/for/master");
setApiUser(user);
- gApi.changes().id(result.getChangeId()).setPrivate(true);
+ gApi.changes().id(result.getChangeId()).setPrivate(true, null);
// Owner can always access its private changes.
assertThat(gApi.changes().id(result.getChangeId()).get().isPrivate).isTrue();
@@ -246,7 +272,7 @@
@Test
public void privateChangeOfOtherUserCanBeAccessedWithPermission() throws Exception {
PushOneCommit.Result result = createChange();
- gApi.changes().id(result.getChangeId()).setPrivate(true);
+ gApi.changes().id(result.getChangeId()).setPrivate(true, null);
allow(Permission.VIEW_PRIVATE_CHANGES, REGISTERED_USERS, "refs/*");
setApiUser(user);
@@ -596,6 +622,50 @@
}
@Test
+ public void rebaseNotAllowedWithoutPushPermission() throws Exception {
+ // Create two changes both with the same parent
+ PushOneCommit.Result r = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ grant(Permission.REBASE, project, "refs/heads/master", false, REGISTERED_USERS);
+ block(Permission.PUSH, REGISTERED_USERS, "refs/for/*");
+
+ // Rebase the second
+ String changeId = r2.getChangeId();
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ exception.expectMessage("rebase not permitted");
+ gApi.changes().id(changeId).rebase();
+ }
+
+ @Test
+ public void rebaseNotAllowedForOwnerWithoutPushPermission() throws Exception {
+ // Create two changes both with the same parent
+ PushOneCommit.Result r = createChange();
+ testRepo.reset("HEAD~1");
+ PushOneCommit.Result r2 = createChange();
+
+ // Approve and submit the first change
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+ revision.review(ReviewInput.approve());
+ revision.submit();
+
+ block(Permission.PUSH, REGISTERED_USERS, "refs/for/*");
+
+ // Rebase the second
+ String changeId = r2.getChangeId();
+ exception.expect(AuthException.class);
+ exception.expectMessage("rebase not permitted");
+ gApi.changes().id(changeId).rebase();
+ }
+
+ @Test
public void publish() throws Exception {
PushOneCommit.Result r = createChange("refs/drafts/master");
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index 0abdde6..3b868dd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -518,7 +518,7 @@
lsRemoteCommand.call().stream().map(Ref::getName).collect(toList());
assertWithMessage("Precondition violated").that(initialRefNames).contains(change3RefName);
- gApi.changes().id(c3.getId().get()).setPrivate(true);
+ gApi.changes().id(c3.getId().get()).setPrivate(true, null);
List<String> refNames = lsRemoteCommand.call().stream().map(Ref::getName).collect(toList());
assertThat(refNames).doesNotContain(change3RefName);
@@ -538,7 +538,7 @@
lsRemoteCommand.call().stream().map(Ref::getName).collect(toList());
assertWithMessage("Precondition violated").that(initialRefNames).contains(change3RefName);
- gApi.changes().id(c3.getId().get()).setPrivate(true);
+ gApi.changes().id(c3.getId().get()).setPrivate(true, null);
List<String> refNames = lsRemoteCommand.call().stream().map(Ref::getName).collect(toList());
assertThat(refNames).contains(change3RefName);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index f6c70b0..77d02fb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -16,10 +16,13 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
@@ -28,9 +31,9 @@
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.GerritConfig;
-import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.common.data.GlobalCapability;
@@ -53,6 +56,7 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.LockFailureException;
import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -75,6 +79,9 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.util.MutableInteger;
import org.junit.Test;
@@ -90,15 +97,7 @@
public void getExternalIds() throws Exception {
Collection<ExternalId> expectedIds = accountCache.get(user.getId()).getExternalIds();
- List<AccountExternalIdInfo> expectedIdInfos = new ArrayList<>();
- for (ExternalId id : expectedIds) {
- AccountExternalIdInfo info = new AccountExternalIdInfo();
- info.identity = id.key().get();
- info.emailAddress = id.email();
- info.canDelete = !id.isScheme(SCHEME_USERNAME) ? true : null;
- info.trusted = true;
- expectedIdInfos.add(info);
- }
+ List<AccountExternalIdInfo> expectedIdInfos = toExternalIdInfos(expectedIds);
RestResponse response = userRestSession.get("/accounts/self/external.ids");
response.assertOK();
@@ -197,19 +196,126 @@
@Test
public void pushToExternalIdsBranch() throws Exception {
- grant(Permission.READ, allUsers, RefNames.REFS_EXTERNAL_IDS);
- grant(Permission.PUSH, allUsers, RefNames.REFS_EXTERNAL_IDS);
-
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":externalIds");
- allUsersRepo.reset("externalIds");
- PushOneCommit push = pushFactory.create(db, admin.getIdent(), allUsersRepo);
- push.to(RefNames.REFS_EXTERNAL_IDS)
- .assertErrorStatus("not allowed to update " + RefNames.REFS_EXTERNAL_IDS);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ // different case email is allowed
+ ExternalId newExtId = createExternalIdWithOtherCaseEmail("foo:bar");
+ addExtId(allUsersRepo, newExtId);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ List<AccountExternalIdInfo> extIdsBefore = gApi.accounts().self().getExternalIds();
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertThat(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS).getStatus()).isEqualTo(Status.OK);
+
+ List<AccountExternalIdInfo> extIdsAfter = gApi.accounts().self().getExternalIds();
+ assertThat(extIdsAfter)
+ .containsExactlyElementsIn(
+ Iterables.concat(extIdsBefore, ImmutableSet.of(toExternalIdInfo(newExtId))));
}
@Test
- @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
+ public void pushToExternalIdsBranchRejectsExternalIdWithoutAccountId() throws Exception {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ insertExternalIdWithoutAccountId(
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertRefUpdateFailure(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS), "invalid external IDs");
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsExternalIdWithKeyThatDoesntMatchTheNoteId()
+ throws Exception {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ insertExternalIdWithKeyThatDoesntMatchNoteId(
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertRefUpdateFailure(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS), "invalid external IDs");
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsExternalIdWithInvalidConfig() throws Exception {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ insertExternalIdWithInvalidConfig(
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertRefUpdateFailure(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS), "invalid external IDs");
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsExternalIdWithEmptyNote() throws Exception {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ insertExternalIdWithEmptyNote(
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertRefUpdateFailure(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS), "invalid external IDs");
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsExternalIdForNonExistingAccount() throws Exception {
+ testPushToExternalIdsBranchRejectsInvalidExternalId(
+ createExternalIdForNonExistingAccount("foo:bar"));
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsExternalIdWithInvalidEmail() throws Exception {
+ testPushToExternalIdsBranchRejectsInvalidExternalId(
+ createExternalIdWithInvalidEmail("foo:bar"));
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsDuplicateEmails() throws Exception {
+ testPushToExternalIdsBranchRejectsInvalidExternalId(
+ createExternalIdWithDuplicateEmail("foo:bar"));
+ }
+
+ @Test
+ public void pushToExternalIdsBranchRejectsBadPassword() throws Exception {
+ testPushToExternalIdsBranchRejectsInvalidExternalId(createExternalIdWithBadPassword("foo"));
+ }
+
+ private void testPushToExternalIdsBranchRejectsInvalidExternalId(ExternalId invalidExtId)
+ throws Exception {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ addExtId(allUsersRepo, invalidExtId);
+ allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
+
+ allowPushOfExternalIds();
+ PushResult r = pushHead(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ assertRefUpdateFailure(r.getRemoteUpdate(RefNames.REFS_EXTERNAL_IDS), "invalid external IDs");
+ }
+
+ @Test
public void readExternalIdsWhenInvalidExternalIdsExist() throws Exception {
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
resetCurrentApiUser();
@@ -217,15 +323,15 @@
insertValidExternalIds();
insertInvalidButParsableExternalIds();
- Set<ExternalId> parseableExtIds = externalIds.all(db);
+ Set<ExternalId> parseableExtIds = externalIds.all();
insertNonParsableExternalIds();
- Set<ExternalId> extIds = externalIds.all(db);
+ Set<ExternalId> extIds = externalIds.all();
assertThat(extIds).containsExactlyElementsIn(parseableExtIds);
for (ExternalId parseableExtId : parseableExtIds) {
- ExternalId extId = externalIds.get(db, parseableExtId.key());
+ ExternalId extId = externalIds.get(parseableExtId.key());
assertThat(extId).isEqualTo(parseableExtId);
}
}
@@ -270,13 +376,12 @@
// create valid external IDs
u.insert(
- db,
ExternalId.createWithPassword(
ExternalId.Key.parse(nextId(scheme, i)),
admin.id,
"admin.other@example.com",
"secret-password"));
- u.insert(db, createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
+ u.insert(createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
}
private Set<ConsistencyProblemInfo> insertInvalidButParsableExternalIds()
@@ -288,7 +393,7 @@
Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
ExternalId extIdForNonExistingAccount =
createExternalIdForNonExistingAccount(nextId(scheme, i));
- u.insert(db, extIdForNonExistingAccount);
+ u.insert(extIdForNonExistingAccount);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -297,7 +402,7 @@
+ extIdForNonExistingAccount.accountId().get()));
ExternalId extIdWithInvalidEmail = createExternalIdWithInvalidEmail(nextId(scheme, i));
- u.insert(db, extIdWithInvalidEmail);
+ u.insert(extIdWithInvalidEmail);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -306,7 +411,7 @@
+ extIdWithInvalidEmail.email()));
ExternalId extIdWithDuplicateEmail = createExternalIdWithDuplicateEmail(nextId(scheme, i));
- u.insert(db, extIdWithDuplicateEmail);
+ u.insert(extIdWithDuplicateEmail);
expectedProblems.add(
consistencyError(
"Email '"
@@ -318,7 +423,7 @@
+ "'"));
ExternalId extIdWithBadPassword = createExternalIdWithBadPassword("admin-username");
- u.insert(db, extIdWithBadPassword);
+ u.insert(extIdWithBadPassword);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -508,7 +613,7 @@
() -> {
if (!doneBgUpdate.getAndSet(true)) {
try {
- extIdsUpdate.create().insert(db, ExternalId.create(barId, admin.id));
+ extIdsUpdate.create().insert(ExternalId.create(barId, admin.id));
} catch (IOException | ConfigInvalidException | OrmException e) {
// Ignore, the successful insertion of the external ID is asserted later
}
@@ -516,11 +621,11 @@
},
retryer);
assertThat(doneBgUpdate.get()).isFalse();
- update.insert(db, ExternalId.create(fooId, admin.id));
+ update.insert(ExternalId.create(fooId, admin.id));
assertThat(doneBgUpdate.get()).isTrue();
- assertThat(externalIds.get(db, fooId)).isNotNull();
- assertThat(externalIds.get(db, barId)).isNotNull();
+ assertThat(externalIds.get(fooId)).isNotNull();
+ assertThat(externalIds.get(barId)).isNotNull();
}
@Test
@@ -544,7 +649,7 @@
try {
extIdsUpdate
.create()
- .insert(db, ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
+ .insert(ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
} catch (IOException | ConfigInvalidException | OrmException e) {
// Ignore, the successful insertion of the external ID is asserted later
}
@@ -555,14 +660,14 @@
.build());
assertThat(bgCounter.get()).isEqualTo(0);
try {
- update.insert(db, ExternalId.create(ExternalId.Key.create("abc", "abc"), admin.id));
+ update.insert(ExternalId.create(ExternalId.Key.create("abc", "abc"), admin.id));
fail("expected LockFailureException");
} catch (LockFailureException e) {
// Ignore, expected
}
assertThat(bgCounter.get()).isEqualTo(extIdsKeys.length);
for (ExternalId.Key extIdKey : extIdsKeys) {
- assertThat(externalIds.get(db, extIdKey)).isNotNull();
+ assertThat(externalIds.get(extIdKey)).isNotNull();
}
}
@@ -570,38 +675,36 @@
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
Account.Id accountId = new Account.Id(1024 * 100);
- extIdsUpdate.create().insert(db, ExternalId.create(extIdKey, accountId));
- ExternalId extId = externalIds.get(db, extIdKey);
+ extIdsUpdate.create().insert(ExternalId.create(extIdKey, accountId));
+ ExternalId extId = externalIds.get(extIdKey);
assertThat(extId.accountId()).isEqualTo(accountId);
}
@Test
- @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
public void checkNoReloadAfterUpdate() throws Exception {
- Set<ExternalId> expectedExtIds = new HashSet<>(externalIds.byAccount(db, admin.id));
+ Set<ExternalId> expectedExtIds = new HashSet<>(externalIds.byAccount(admin.id));
externalIdReader.setFailOnLoad(true);
// insert external ID
ExternalId extId = ExternalId.create("foo", "bar", admin.id);
- extIdsUpdate.create().insert(db, extId);
+ extIdsUpdate.create().insert(extId);
expectedExtIds.add(extId);
- assertThat(externalIds.byAccount(db, admin.id)).containsExactlyElementsIn(expectedExtIds);
+ assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
// update external ID
expectedExtIds.remove(extId);
extId = ExternalId.createWithEmail("foo", "bar", admin.id, "foo.bar@example.com");
- extIdsUpdate.create().upsert(db, extId);
+ extIdsUpdate.create().upsert(extId);
expectedExtIds.add(extId);
- assertThat(externalIds.byAccount(db, admin.id)).containsExactlyElementsIn(expectedExtIds);
+ assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
// delete external ID
- extIdsUpdate.create().delete(db, extId);
+ extIdsUpdate.create().delete(extId);
expectedExtIds.remove(extId);
- assertThat(externalIds.byAccount(db, admin.id)).containsExactlyElementsIn(expectedExtIds);
+ assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
}
@Test
- @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
public void byAccountFailIfReadingExternalIdsFails() throws Exception {
externalIdReader.setFailOnLoad(true);
@@ -609,11 +712,10 @@
insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id));
exception.expect(IOException.class);
- externalIds.byAccount(db, admin.id);
+ externalIds.byAccount(admin.id);
}
@Test
- @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
public void byEmailFailIfReadingExternalIdsFails() throws Exception {
externalIdReader.setFailOnLoad(true);
@@ -621,17 +723,16 @@
insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id));
exception.expect(IOException.class);
- externalIds.byEmail(db, admin.email);
+ externalIds.byEmail(admin.email);
}
@Test
- @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
public void byAccountUpdateExternalIdsBehindGerritsBack() throws Exception {
- Set<ExternalId> expectedExternalIds = new HashSet<>(externalIds.byAccount(db, admin.id));
+ Set<ExternalId> expectedExternalIds = new HashSet<>(externalIds.byAccount(admin.id));
ExternalId newExtId = ExternalId.create("foo", "bar", admin.id);
insertExtIdBehindGerritsBack(newExtId);
expectedExternalIds.add(newExtId);
- assertThat(externalIds.byAccount(db, admin.id)).containsExactlyElementsIn(expectedExternalIds);
+ assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExternalIds);
}
private void insertExtIdBehindGerritsBack(ExternalId extId) throws Exception {
@@ -645,4 +746,54 @@
repo, rw, ins, rev, noteMap, "insert new ID", serverIdent.get(), serverIdent.get());
}
}
+
+ private void addExtId(TestRepository<?> testRepo, ExternalId... extIds)
+ throws IOException, OrmDuplicateKeyException, ConfigInvalidException {
+ ObjectId rev = ExternalIdReader.readRevision(testRepo.getRepository());
+
+ try (ObjectInserter ins = testRepo.getRepository().newObjectInserter()) {
+ NoteMap noteMap = ExternalIdReader.readNoteMap(testRepo.getRevWalk(), rev);
+ for (ExternalId extId : extIds) {
+ ExternalIdsUpdate.insert(testRepo.getRevWalk(), ins, noteMap, extId);
+ }
+
+ ExternalIdsUpdate.commit(
+ testRepo.getRepository(),
+ testRepo.getRevWalk(),
+ ins,
+ rev,
+ noteMap,
+ "Add external ID",
+ admin.getIdent(),
+ admin.getIdent());
+ }
+ }
+
+ private List<AccountExternalIdInfo> toExternalIdInfos(Collection<ExternalId> extIds) {
+ return extIds.stream().map(this::toExternalIdInfo).collect(toList());
+ }
+
+ private AccountExternalIdInfo toExternalIdInfo(ExternalId extId) {
+ AccountExternalIdInfo info = new AccountExternalIdInfo();
+ info.identity = extId.key().get();
+ info.emailAddress = extId.email();
+ info.canDelete = !extId.isScheme(SCHEME_USERNAME) ? true : null;
+ info.trusted =
+ extId.isScheme(SCHEME_MAILTO)
+ || extId.isScheme(SCHEME_UUID)
+ || extId.isScheme(SCHEME_USERNAME)
+ ? true
+ : null;
+ return info;
+ }
+
+ private void allowPushOfExternalIds() throws IOException, ConfigInvalidException {
+ grant(Permission.READ, allUsers, RefNames.REFS_EXTERNAL_IDS);
+ grant(Permission.PUSH, allUsers, RefNames.REFS_EXTERNAL_IDS);
+ }
+
+ private void assertRefUpdateFailure(RemoteRefUpdate update, String msg) {
+ assertThat(update.getStatus()).isEqualTo(Status.REJECTED_OTHER_REASON);
+ assertThat(update.getMessage()).isEqualTo(msg);
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 153e70b..461e6bc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -16,11 +16,14 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
@@ -40,9 +43,11 @@
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.common.RevisionInfo;
+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.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
@@ -52,6 +57,7 @@
import com.google.gerrit.testutil.TestTimeUtil;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@@ -170,6 +176,29 @@
}
@Test
+ public void createChangeWithoutAccessToParentCommitFails() throws Exception {
+ Map<String, PushOneCommit.Result> results =
+ changeInTwoBranches("invisible-branch", "a.txt", "visible-branch", "b.txt");
+ block(READ, REGISTERED_USERS, "refs/heads/invisible-branch", project);
+
+ ChangeInput in = newChangeInput(ChangeStatus.NEW);
+ in.branch = "visible-branch";
+ in.baseChange = results.get("invisible-branch").getChangeId();
+ assertCreateFails(
+ in, UnprocessableEntityException.class, "Base change not found: " + in.baseChange);
+ }
+
+ @Test
+ public void createChangeOnInvisibleBranchFails() throws Exception {
+ changeInTwoBranches("invisible-branch", "a.txt", "branchB", "b.txt");
+ block(READ, REGISTERED_USERS, "refs/heads/invisible-branch", project);
+
+ ChangeInput in = newChangeInput(ChangeStatus.NEW);
+ in.branch = "invisible-branch";
+ assertCreateFails(in, AuthException.class, "cannot upload review");
+ }
+
+ @Test
public void noteDbCommit() throws Exception {
assume().that(notesMigration.readChanges()).isTrue();
@@ -444,8 +473,18 @@
return in;
}
- private void changeInTwoBranches(String branchA, String fileA, String branchB, String fileB)
- throws Exception {
+ /**
+ * Create an empty commit in master, two new branches with one commit each.
+ *
+ * @param branchA name of first branch to create
+ * @param fileA name of file to commit to branchA
+ * @param branchB name of second branch to create
+ * @param fileB name of file to commit to branchB
+ * @return A {@code Map} of branchName => commit result.
+ * @throws Exception
+ */
+ private Map<String, Result> changeInTwoBranches(
+ String branchA, String fileA, String branchB, String fileB) throws Exception {
// create a initial commit in master
Result initialCommit =
pushFactory
@@ -470,5 +509,7 @@
commitB.setParent(initialCommit.getCommit());
Result changeB = commitB.to("refs/heads/" + branchB);
changeB.assertOkStatus();
+
+ return ImmutableMap.of("master", initialCommit, branchA, changeA, branchB, changeB);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 04151e9..0ac263f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -30,9 +30,8 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.InputStream;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -547,10 +546,10 @@
try (BinaryResult request = submitPreview(change1.getChangeId(), "tgz")) {
assertThat(request.getContentType()).isEqualTo("application/x-gzip");
tempfile = File.createTempFile("test", null);
- request.writeTo(new FileOutputStream(tempfile));
+ request.writeTo(Files.newOutputStream(tempfile.toPath()));
}
- InputStream is = new GZIPInputStream(new FileInputStream(tempfile));
+ InputStream is = new GZIPInputStream(Files.newInputStream(tempfile.toPath()));
List<String> untarredFiles = new ArrayList<>();
try (TarArchiveInputStream tarInputStream =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 26a49b1..16dbee3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -20,12 +20,14 @@
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
@@ -34,27 +36,40 @@
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.notedb.ChangeNoteUtil;
+import com.google.gerrit.server.notedb.DeleteCommentRewriter;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +82,8 @@
@Inject private FakeEmailSender email;
+ @Inject private ChangeNoteUtil noteUtil;
+
private final Integer[] lines = {0, 1};
@Before
@@ -739,6 +756,167 @@
}
}
+ @Test
+ public void deleteCommentCannotBeAppliedByUser() throws Exception {
+ PushOneCommit.Result result = createChange();
+ CommentInput targetComment = addComment(result.getChangeId(), "My password: abc123");
+
+ Map<String, List<CommentInfo>> commentsMap =
+ getPublishedComments(result.getChangeId(), result.getCommit().name());
+
+ assertThat(commentsMap.size()).isEqualTo(1);
+ assertThat(commentsMap.get(FILE_NAME)).hasSize(1);
+
+ String uuid = commentsMap.get(targetComment.path).get(0).id;
+ DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.changes().id(result.getChangeId()).current().comment(uuid).delete(input);
+ }
+
+ @Test
+ public void deleteCommentByRewritingCommitHistory() throws Exception {
+ // Create change (the 1st commit on the change's meta branch).
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ Change.Id id = result.getChange().getId();
+
+ // Add two comments in patch set 1 (the 2nd commit on the change's meta branch).
+ ReviewInput reviewInput = new ReviewInput();
+ CommentInput comment1 = newComment(FILE_NAME, Side.REVISION, 0, "My password: abc123", false);
+ CommentInput comment2 = newComment(FILE_NAME, Side.REVISION, 1, "nit: long line", false);
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, Lists.newArrayList(comment1, comment2));
+ reviewInput.label("Code-Review", 1);
+ gApi.changes().id(changeId).current().review(reviewInput);
+
+ // Create patch set 2 (the 3rd commit on the change's meta branch).
+ amendChange(changeId);
+
+ // Add 'comment3' in patch set 2 (the 4th commit on the change's meta branch).
+ CommentInput comment3 = addComment(changeId, "typo");
+
+ Map<String, List<CommentInfo>> commentsMap = getPublishedComments(changeId);
+ assertThat(commentsMap).hasSize(1);
+ assertThat(commentsMap.get(FILE_NAME)).hasSize(3);
+ Optional<CommentInfo> targetCommentInfo =
+ commentsMap
+ .get(FILE_NAME)
+ .stream()
+ .filter(c -> c.message.equals("My password: abc123"))
+ .findFirst();
+ assertThat(targetCommentInfo.isPresent()).isTrue();
+
+ List<RevCommit> commitsBeforeDelete = new ArrayList<>();
+ if (notesMigration.commitChangeWrites()) {
+ commitsBeforeDelete = getCommits(id);
+ }
+
+ String uuid = targetCommentInfo.get().id;
+ // Get the target comment.
+ CommentInfo oldComment =
+ gApi.changes().id(changeId).revision(result.getCommit().getName()).comment(uuid).get();
+
+ // Delete the target comment.
+ DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
+ setApiUser(admin);
+ CommentInfo updatedComment =
+ gApi.changes()
+ .id(changeId)
+ .revision(result.getCommit().getName())
+ .comment(uuid)
+ .delete(input);
+
+ String expectedMsg =
+ String.format("Comment removed by: %s; Reason: %s", admin.fullName, input.reason);
+
+ assertThat(updatedComment.message).isEqualTo(expectedMsg);
+ updatedComment.message = oldComment.message;
+ assertThat(updatedComment).isEqualTo(oldComment);
+
+ // Check the comment's message has been replaced in NoteDb.
+ if (notesMigration.commitChangeWrites()) {
+ assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg);
+ }
+
+ // Make sure that comments can still be added correctly.
+ CommentInput comment4 = addComment(changeId, "too much space");
+ commentsMap = getPublishedComments(changeId);
+
+ assertThat(commentsMap).hasSize(1);
+ List<CommentInput> comments =
+ Lists.transform(commentsMap.get(FILE_NAME), infoToInput(FILE_NAME));
+
+ // Change comment1's message to the expected message.
+ comment1.message = expectedMsg;
+ assertThat(comments).containsExactly(comment1, comment2, comment3, comment4);
+ }
+
+ private CommentInput addComment(String changeId, String message) throws Exception {
+ ReviewInput input = new ReviewInput();
+ CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, message, false);
+ input.comments = ImmutableMap.of(comment.path, Lists.newArrayList(comment));
+ gApi.changes().id(changeId).current().review(input);
+ return comment;
+ }
+
+ private List<RevCommit> getCommits(Change.Id changeId) throws IOException {
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(repo)) {
+ Ref metaRef = repo.exactRef(RefNames.changeMetaRef(changeId));
+ revWalk.markStart(revWalk.parseCommit(metaRef.getObjectId()));
+ return Lists.newArrayList(revWalk);
+ }
+ }
+
+ /**
+ * All the commits, which contain the target comment before, should still contain the comment with
+ * the updated message. All the other metas of the commits should be exactly the same.
+ */
+ private void assertMetaBranchCommitsAfterRewriting(
+ List<RevCommit> beforeDelete,
+ Change.Id changeId,
+ String targetCommentUuid,
+ String expectedMessage)
+ throws Exception {
+ List<RevCommit> afterDelete = getCommits(changeId);
+ assertThat(afterDelete).hasSize(beforeDelete.size());
+
+ try (Repository repo = repoManager.openRepository(project);
+ ObjectReader reader = repo.newObjectReader()) {
+ for (int i = 0; i < beforeDelete.size(); i++) {
+ RevCommit commitBefore = beforeDelete.get(i);
+ RevCommit commitAfter = afterDelete.get(i);
+
+ Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapBefore =
+ DeleteCommentRewriter.getPublishedComments(
+ noteUtil, changeId, reader, NoteMap.read(reader, commitBefore));
+ Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapAfter =
+ DeleteCommentRewriter.getPublishedComments(
+ noteUtil, changeId, reader, NoteMap.read(reader, commitAfter));
+
+ if (commentMapBefore.containsKey(targetCommentUuid)) {
+ assertThat(commentMapAfter).containsKey(targetCommentUuid);
+ com.google.gerrit.reviewdb.client.Comment comment =
+ commentMapAfter.get(targetCommentUuid);
+ assertThat(comment.message).isEqualTo(expectedMessage);
+ comment.message = commentMapBefore.get(targetCommentUuid).message;
+ commentMapAfter.put(targetCommentUuid, comment);
+ assertThat(commentMapAfter).isEqualTo(commentMapBefore);
+ } else {
+ assertThat(commentMapAfter).doesNotContainKey(targetCommentUuid);
+ }
+
+ // Other metas should be exactly the same.
+ assertThat(commitAfter.getFullMessage()).isEqualTo(commitBefore.getFullMessage());
+ assertThat(commitAfter.getCommitterIdent()).isEqualTo(commitBefore.getCommitterIdent());
+ assertThat(commitAfter.getAuthorIdent()).isEqualTo(commitBefore.getAuthorIdent());
+ assertThat(commitAfter.getEncoding()).isEqualTo(commitBefore.getEncoding());
+ assertThat(commitAfter.getEncodingName()).isEqualTo(commitBefore.getEncodingName());
+ }
+ }
+ }
+
private static String extractComments(String msg) {
// Extract lines between start "....." and end "-- ".
Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL);
@@ -803,6 +981,10 @@
return gApi.changes().id(changeId).revision(revId).drafts();
}
+ private Map<String, List<CommentInfo>> getPublishedComments(String changeId) throws Exception {
+ return gApi.changes().id(changeId).comments();
+ }
+
private CommentInfo getDraftComment(String changeId, String revId, String uuid) throws Exception {
return gApi.changes().id(changeId).revision(revId).draft(uuid).get();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 8d7a452..3dabced 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -85,7 +86,7 @@
void move(MoveInput in) throws RestApiException;
- void setPrivate(boolean value) throws RestApiException;
+ void setPrivate(boolean value, @Nullable String message) throws RestApiException;
void setWorkInProgress(String message) throws RestApiException;
@@ -335,7 +336,7 @@
}
@Override
- public void setPrivate(boolean value) {
+ public void setPrivate(boolean value, @Nullable String message) {
throw new NotImplementedException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java
index 78f2b89..46827e5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java
@@ -22,6 +22,17 @@
CommentInfo get() throws RestApiException;
/**
+ * Deletes a published comment of a revision. For NoteDb, it deletes the comment by rewriting the
+ * commit history.
+ *
+ * <p>Note instead of deleting the whole comment, this endpoint just replaces the comment's
+ * message.
+ *
+ * @return the comment with its message updated.
+ */
+ CommentInfo delete(DeleteCommentInput input) throws RestApiException;
+
+ /**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
*/
@@ -30,5 +41,10 @@
public CommentInfo get() {
throw new NotImplementedException();
}
+
+ @Override
+ public CommentInfo delete(DeleteCommentInput input) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteCommentInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteCommentInput.java
new file mode 100644
index 0000000..75fd16b
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteCommentInput.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class DeleteCommentInput {
+ @DefaultInput public String reason;
+
+ public DeleteCommentInput() {
+ reason = "";
+ }
+
+ public DeleteCommentInput(String reason) {
+ this.reason = Strings.nullToEmpty(reason);
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GroupIndexedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GroupIndexedListener.java
new file mode 100644
index 0000000..d499020
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GroupIndexedListener.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever a group is indexed */
+@ExtensionPoint
+public interface GroupIndexedListener {
+ /**
+ * Invoked when a group is indexed
+ *
+ * @param uuid of the group
+ */
+ void onGroupIndexed(String uuid);
+}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index 64286c4..4e6ea66 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -23,7 +23,6 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.gpg.server.DeleteGpgKey.Input;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -43,7 +42,6 @@
public static class Input {}
private final Provider<PersonIdent> serverIdent;
- private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
private final AccountCache accountCache;
private final ExternalIdsUpdate.User externalIdsUpdateFactory;
@@ -51,12 +49,10 @@
@Inject
DeleteGpgKey(
@GerritPersonIdent Provider<PersonIdent> serverIdent,
- Provider<ReviewDb> db,
Provider<PublicKeyStore> storeProvider,
AccountCache accountCache,
ExternalIdsUpdate.User externalIdsUpdateFactory) {
this.serverIdent = serverIdent;
- this.db = db;
this.storeProvider = storeProvider;
this.accountCache = accountCache;
this.externalIdsUpdateFactory = externalIdsUpdateFactory;
@@ -70,7 +66,6 @@
externalIdsUpdateFactory
.create()
.delete(
- db.get(),
rsrc.getUser().getAccountId(),
ExternalId.Key.create(
SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint())));
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
index 13fb368..678247e 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -34,7 +34,6 @@
import com.google.gerrit.gpg.GerritPublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -64,7 +63,6 @@
public static final String MIME_TYPE = "application/pgp-keys";
private final DynamicMap<RestView<GpgKey>> views;
- private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
private final Provider<PublicKeyStore> storeProvider;
private final GerritPublicKeyChecker.Factory checkerFactory;
@@ -73,13 +71,11 @@
@Inject
GpgKeys(
DynamicMap<RestView<GpgKey>> views,
- Provider<ReviewDb> db,
Provider<CurrentUser> self,
Provider<PublicKeyStore> storeProvider,
GerritPublicKeyChecker.Factory checkerFactory,
ExternalIds externalIds) {
this.views = views;
- this.db = db;
this.self = self;
this.storeProvider = storeProvider;
this.checkerFactory = checkerFactory;
@@ -199,8 +195,8 @@
}
}
- private Iterable<ExternalId> getGpgExtIds(AccountResource rsrc) throws IOException, OrmException {
- return externalIds.byAccount(db.get(), rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
+ private Iterable<ExternalId> getGpgExtIds(AccountResource rsrc) throws IOException {
+ return externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
}
private static long keyId(byte[] fp) {
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 9c04ced..af4d6bb 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -40,7 +40,6 @@
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.gpg.server.PostGpgKeys.Input;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
@@ -85,7 +84,6 @@
private final Logger log = LoggerFactory.getLogger(getClass());
private final Provider<PersonIdent> serverIdent;
- private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
private final Provider<PublicKeyStore> storeProvider;
private final GerritPublicKeyChecker.Factory checkerFactory;
@@ -98,7 +96,6 @@
@Inject
PostGpgKeys(
@GerritPersonIdent Provider<PersonIdent> serverIdent,
- Provider<ReviewDb> db,
Provider<CurrentUser> self,
Provider<PublicKeyStore> storeProvider,
GerritPublicKeyChecker.Factory checkerFactory,
@@ -108,7 +105,6 @@
ExternalIds externalIds,
ExternalIdsUpdate.User externalIdsUpdateFactory) {
this.serverIdent = serverIdent;
- this.db = db;
this.self = self;
this.storeProvider = storeProvider;
this.checkerFactory = checkerFactory;
@@ -126,7 +122,7 @@
GpgKeys.checkVisible(self, rsrc);
Collection<ExternalId> existingExtIds =
- externalIds.byAccount(db.get(), rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
+ externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
try (PublicKeyStore store = storeProvider.get()) {
Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, toRemove);
@@ -151,7 +147,7 @@
toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList());
externalIdsUpdateFactory
.create()
- .replace(db.get(), rsrc.getUser().getAccountId(), extIdKeysToRemove, newExtIds);
+ .replace(rsrc.getUser().getAccountId(), extIdKeysToRemove, newExtIds);
accountCache.evict(rsrc.getUser().getAccountId());
return toJson(newKeys, toRemove, store, rsrc.getUser());
}
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index d82f95b..deb0dc4 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -223,7 +223,7 @@
@Test
public void noExternalIds() throws Exception {
ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
- externalIdsUpdate.deleteAll(db, user.getAccountId());
+ externalIdsUpdate.deleteAll(user.getAccountId());
reloadUser();
TestKey key = validKeyWithSecondUserId();
@@ -237,7 +237,7 @@
assertProblems(
checker.check(key.getPublicKey()), Status.BAD, "Key is not associated with any users");
externalIdsUpdate.insert(
- db, ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
+ ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
reloadUser();
assertProblems(checker.check(key.getPublicKey()), Status.BAD, "No identities found for user");
}
@@ -406,7 +406,7 @@
cb.setCommitter(ident);
assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
- externalIdsUpdateFactory.create().insert(db, newExtIds);
+ externalIdsUpdateFactory.create().insert(newExtIds);
accountCache.evict(user.getAccountId());
}
@@ -432,7 +432,7 @@
private void addExternalId(String scheme, String id, String email) throws Exception {
externalIdsUpdateFactory
.create()
- .insert(db, ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
+ .insert(ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
reloadUser();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
index 74668c1..234df60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
@@ -41,6 +41,11 @@
@Override
public void onSuccess(JavaScriptObject in) {
UiResult result = asUiResult(in);
+ if (result == null) {
+ Gerrit.display(target);
+ return;
+ }
+
if (result.alert() != null) {
Window.alert(result.alert());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index f985f31..915867d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -122,11 +122,11 @@
}
public static void markPrivate(int id, AsyncCallback<JavaScriptObject> cb) {
- change(id).view("private").put(cb);
+ change(id).view("private").post(PrivateInput.create(), cb);
}
public static void unmarkPrivate(int id, AsyncCallback<JavaScriptObject> cb) {
- change(id).view("private").delete(cb);
+ change(id).view("private.delete").post(PrivateInput.create(), cb);
}
public static RestApi comments(int id) {
@@ -327,6 +327,16 @@
protected CherryPickInput() {}
}
+ private static class PrivateInput extends JavaScriptObject {
+ static PrivateInput create() {
+ return (PrivateInput) createObject();
+ }
+
+ final native void setMessage(String m) /*-{ this.message = m; }-*/;
+
+ protected PrivateInput() {}
+ }
+
private static class RebaseInput extends JavaScriptObject {
final native void setBase(String b) /*-{ this.base = b; }-*/;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java
index 651d718..90aedbe 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java
@@ -16,9 +16,10 @@
import com.google.gwtexpui.linker.server.UserAgentRule;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashSet;
@@ -102,7 +103,7 @@
mkdir(rawtmp.getParentFile());
rawtmp.deleteOnExit();
- try (FileOutputStream rawout = new FileOutputStream(rawtmp);
+ try (OutputStream rawout = Files.newOutputStream(rawtmp.toPath());
InputStream in = zf.getInputStream(ze)) {
final byte[] buf = new byte[4096];
int n;
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index a7af056..b22ba49 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -20,9 +20,9 @@
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -293,7 +293,7 @@
private static void extractJar(ZipFile zf, ZipEntry ze, SortedMap<String, URL> jars)
throws IOException {
File tmp = createTempFile(safeName(ze), ".jar");
- try (FileOutputStream out = new FileOutputStream(tmp);
+ try (OutputStream out = Files.newOutputStream(tmp.toPath());
InputStream in = zf.getInputStream(ze)) {
byte[] buf = new byte[4096];
int n;
@@ -414,7 +414,7 @@
if (src != null) {
try (InputStream in = src.getLocation().openStream()) {
final File tmp = createTempFile("gerrit_", ".zip");
- try (FileOutputStream out = new FileOutputStream(tmp)) {
+ try (OutputStream out = Files.newOutputStream(tmp.toPath())) {
final byte[] buf = new byte[4096];
int n;
while ((n = in.read(buf, 0, buf.length)) > 0) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 4e18ddc..3da3596 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -19,13 +19,11 @@
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.account.externalids.ExternalIdsBatchUpdate;
import com.google.gerrit.server.schema.SchemaVersionCheck;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -38,8 +36,6 @@
private final LifecycleManager manager = new LifecycleManager();
private final TextProgressMonitor monitor = new TextProgressMonitor();
- @Inject private SchemaFactory<ReviewDb> database;
-
@Inject private ExternalIds externalIds;
@Inject private ExternalIdsBatchUpdate externalIdsBatchUpdate;
@@ -63,19 +59,17 @@
})
.injectMembers(this);
- try (ReviewDb db = database.open()) {
- Collection<ExternalId> todo = externalIds.all(db);
- monitor.beginTask("Converting local usernames", todo.size());
+ Collection<ExternalId> todo = externalIds.all();
+ monitor.beginTask("Converting local usernames", todo.size());
- for (ExternalId extId : todo) {
- convertLocalUserToLowerCase(extId);
- monitor.update(1);
- }
-
- externalIdsBatchUpdate.commit(db, "Convert local usernames to lower case");
- monitor.endTask();
- manager.stop();
+ for (ExternalId extId : todo) {
+ convertLocalUserToLowerCase(extId);
+ monitor.update(1);
}
+
+ externalIdsBatchUpdate.commit("Convert local usernames to lower case");
+ monitor.endTask();
+ manager.stop();
return 0;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
index fb524a3..d970856 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
@@ -39,10 +39,10 @@
import com.google.protobuf.UnknownFieldSet;
import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.nio.file.Files;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -96,7 +96,7 @@
}
Parser<UnknownFieldSet> parser = UnknownFieldSet.getDefaultInstance().getParserForType();
- try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
+ try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
UnknownFieldSet msg;
while ((msg = parser.parseDelimitedFrom(in)) != null) {
Map.Entry<Integer, UnknownFieldSet.Field> e =
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 38c3321..c7606d6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -277,6 +277,7 @@
}
c.setInheritChannel(cfg.getBoolean("httpd", "inheritChannel", false));
c.setReuseAddress(reuseAddress);
+ c.setIdleTimeout(cfg.getTimeUnit("httpd", null, "idleTimeout", 30000L, MILLISECONDS));
connectors[idx] = c;
}
return connectors;
@@ -398,12 +399,12 @@
//
app.setContextPath(contextPath);
- // HTTP front-end filter to be used as surrogate of Apache HTTP
+ // HTTP front-end filters to be used as surrogate of Apache HTTP
// reverse-proxy filtering.
// It is meant to be used as simpler tiny deployment of custom-made
// security enforcement (Security tokens, IP-based security filtering, others)
- String filterClassName = cfg.getString("httpd", null, "filterClass");
- if (filterClassName != null) {
+ String[] filterClassNames = cfg.getStringList("httpd", null, "filterClass");
+ for (String filterClassName : filterClassNames) {
try {
@SuppressWarnings("unchecked")
Class<? extends Filter> filterClass =
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
index 5bedb1b..9906089 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
@@ -14,10 +14,7 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.server.account.externalids.ExternalId.toAccountExternalIds;
-
import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdReader;
@@ -52,9 +49,8 @@
this.allUsers = allUsers.get();
}
- public synchronized void insert(ReviewDb db, String commitMessage, Collection<ExternalId> extIds)
+ public synchronized void insert(String commitMessage, Collection<ExternalId> extIds)
throws OrmException, IOException, ConfigInvalidException {
- db.accountExternalIds().insert(toAccountExternalIds(extIds));
File path = getPath();
if (path != null) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 1e6bfa8..466404b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -104,7 +104,7 @@
if (email != null) {
extIds.add(ExternalId.createEmail(id, email));
}
- externalIds.insert(db, "Add external IDs for initial admin user", extIds);
+ externalIds.insert("Add external IDs for initial admin user", extIds);
Account a = new Account(id, TimeUtil.nowTs());
a.setFullName(name);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index a563a55..361e4b6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -14,8 +14,6 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.common.FileUtil.chmod;
-import static com.google.gerrit.pgm.init.api.InitUtil.die;
import static com.google.gerrit.pgm.init.api.InitUtil.hostname;
import static java.nio.file.Files.exists;
@@ -30,10 +28,6 @@
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.net.InetSocketAddress;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
/** Initialize the {@code sshd} configuration section. */
@Singleton
@@ -86,20 +80,21 @@
}
private void generateSshHostKeys() throws InterruptedException, IOException {
- if (!exists(site.ssh_key) //
- && !exists(site.ssh_rsa) //
- && !exists(site.ssh_dsa)) {
+ if (!exists(site.ssh_key)
+ && (!exists(site.ssh_rsa)
+ || !exists(site.ssh_dsa)
+ || !exists(site.ssh_ed25519)
+ || !exists(site.ssh_ecdsa))) {
System.err.print("Generating SSH host key ...");
System.err.flush();
- if (SecurityUtils.isBouncyCastleRegistered()) {
- // Generate the SSH daemon host key using ssh-keygen.
- //
- final String comment = "gerrit-code-review@" + hostname();
+ // Generate the SSH daemon host key using ssh-keygen.
+ //
+ final String comment = "gerrit-code-review@" + hostname();
- // Workaround for JDK-6518827 - zero-length argument ignored on Win32
- String emptyPassphraseArg = HostPlatform.isWin32() ? "\"\"" : "";
-
+ // Workaround for JDK-6518827 - zero-length argument ignored on Win32
+ String emptyPassphraseArg = HostPlatform.isWin32() ? "\"\"" : "";
+ if (!exists(site.ssh_rsa)) {
System.err.print(" rsa...");
System.err.flush();
new ProcessBuilder(
@@ -117,7 +112,9 @@
.redirectOutput(Redirect.INHERIT)
.start()
.waitFor();
+ }
+ if (!exists(site.ssh_dsa)) {
System.err.print(" dsa...");
System.err.flush();
new ProcessBuilder(
@@ -135,42 +132,57 @@
.redirectOutput(Redirect.INHERIT)
.start()
.waitFor();
+ }
- } else {
- // Generate the SSH daemon host key ourselves. This is complex
- // because SimpleGeneratorHostKeyProvider doesn't mark the data
- // file as only readable by us, exposing the private key for a
- // short period of time. We try to reduce that risk by creating
- // the key within a temporary directory.
- //
- Path tmpdir = site.etc_dir.resolve("tmp.sshkeygen");
- try {
- Files.createDirectory(tmpdir);
- } catch (IOException e) {
- throw die("Cannot create directory " + tmpdir, e);
- }
- chmod(0600, tmpdir);
-
- Path tmpkey = tmpdir.resolve(site.ssh_key.getFileName().toString());
- SimpleGeneratorHostKeyProvider p;
-
- System.err.print(" rsa(simple)...");
+ if (!exists(site.ssh_ed25519)) {
+ System.err.print(" ed25519...");
System.err.flush();
- p = new SimpleGeneratorHostKeyProvider();
- p.setPath(tmpkey.toAbsolutePath());
- p.setAlgorithm("RSA");
- p.loadKeys(); // forces the key to generate.
- chmod(0600, tmpkey);
-
try {
- Files.move(tmpkey, site.ssh_key);
- } catch (IOException e) {
- throw die("Cannot rename " + tmpkey + " to " + site.ssh_key, e);
+ new ProcessBuilder(
+ "ssh-keygen",
+ "-q" /* quiet */,
+ "-t",
+ "ed25519",
+ "-P",
+ emptyPassphraseArg,
+ "-C",
+ comment,
+ "-f",
+ site.ssh_ed25519.toAbsolutePath().toString())
+ .redirectError(Redirect.INHERIT)
+ .redirectOutput(Redirect.INHERIT)
+ .start()
+ .waitFor();
+ } catch (Exception e) {
+ // continue since older hosts won't be able to generate ed25519 keys.
+ System.err.print(" Failed to generate ed25519 key, continuing...");
+ System.err.flush();
}
+ }
+
+ if (!exists(site.ssh_ecdsa)) {
+ System.err.print(" ecdsa...");
+ System.err.flush();
try {
- Files.delete(tmpdir);
- } catch (IOException e) {
- throw die("Cannot delete " + tmpdir, e);
+ new ProcessBuilder(
+ "ssh-keygen",
+ "-q" /* quiet */,
+ "-t",
+ "ecdsa",
+ "-P",
+ emptyPassphraseArg,
+ "-C",
+ comment,
+ "-f",
+ site.ssh_ecdsa.toAbsolutePath().toString())
+ .redirectError(Redirect.INHERIT)
+ .redirectOutput(Redirect.INHERIT)
+ .start()
+ .waitFor();
+ } catch (Exception e) {
+ // continue since older hosts won't be able to generate ecdsa keys.
+ System.err.print(" Failed to generate ecdsa key, continuing...");
+ System.err.flush();
}
}
System.err.println(" done");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/StaleLibraryRemover.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/StaleLibraryRemover.java
index b713067..c454cce 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/StaleLibraryRemover.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/StaleLibraryRemover.java
@@ -49,9 +49,14 @@
for (Path p : paths) {
String old = p.getFileName().toString();
String bak = "." + old + ".backup";
+ Path dest = p.resolveSibling(bak);
+ if (Files.exists(dest)) {
+ ui.message("WARNING: not renaming %s to %s: already exists\n", old, bak);
+ continue;
+ }
ui.message("Renaming %s to %s\n", old, bak);
try {
- Files.move(p, p.resolveSibling(bak));
+ Files.move(p, dest);
} catch (IOException e) {
throw new Die("cannot rename " + old, e);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
index 0ea0f1a..4ad7701 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/rules/PrologCompiler.java
@@ -24,10 +24,9 @@
import com.googlecode.prolog_cafe.compiler.Compiler;
import com.googlecode.prolog_cafe.exceptions.CompileException;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
@@ -134,7 +133,7 @@
// Any leak of tmp caused by this method failing will be cleaned
// up by our caller when tempDir is recursively deleted.
File tmp = File.createTempFile("rules", ".pl", tempDir);
- try (FileOutputStream out = new FileOutputStream(tmp)) {
+ try (OutputStream out = Files.newOutputStream(tmp.toPath())) {
git.open(blobId).copyTo(out);
}
return tmp;
@@ -230,7 +229,7 @@
jarAdd.setTime(now);
out.putNextEntry(jarAdd);
if (f.isFile()) {
- try (FileInputStream in = new FileInputStream(f)) {
+ try (InputStream in = Files.newInputStream(f.toPath())) {
while (true) {
int nRead = in.read(buffer, 0, buffer.length);
if (nRead <= 0) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
deleted file mode 100644
index 3c8f2fa..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (C) 2008 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 com.google.gerrit.extensions.client.AuthType;
-import com.google.gwtorm.client.Column;
-import com.google.gwtorm.client.StringKey;
-import java.util.Objects;
-
-/** Association of an external account identifier to a local {@link Account}. */
-public final class AccountExternalId {
- /**
- * Scheme used for {@link AuthType#LDAP}, {@link AuthType#CLIENT_SSL_CERT_LDAP}, {@link
- * AuthType#HTTP_LDAP}, and {@link AuthType#LDAP_BIND} usernames.
- *
- * <p>The name {@code gerrit:} was a very poor choice.
- */
- public static final String SCHEME_GERRIT = "gerrit:";
-
- /** Scheme used for randomly created identities constructed by a UUID. */
- public static final String SCHEME_UUID = "uuid:";
-
- /** Scheme used to represent only an email address. */
- public static final String SCHEME_MAILTO = "mailto:";
-
- /** Scheme for the username used to authenticate an account, e.g. over SSH. */
- public static final String SCHEME_USERNAME = "username:";
-
- /** Scheme used for GPG public keys. */
- public static final String SCHEME_GPGKEY = "gpgkey:";
-
- /** Scheme for external auth used during authentication, e.g. OAuth Token */
- public static final String SCHEME_EXTERNAL = "external:";
-
- public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- @Column(id = 1)
- protected String externalId;
-
- protected Key() {}
-
- public Key(String scheme, final String identity) {
- if (!scheme.endsWith(":")) {
- scheme += ":";
- }
- externalId = scheme + identity;
- }
-
- public Key(final String e) {
- externalId = e;
- }
-
- @Override
- public String get() {
- return externalId;
- }
-
- @Override
- protected void set(String newValue) {
- externalId = newValue;
- }
-
- public String getScheme() {
- int c = externalId.indexOf(':');
- return 0 < c ? externalId.substring(0, c) : null;
- }
- }
-
- @Column(id = 1, name = Column.NONE)
- protected Key key;
-
- @Column(id = 2)
- protected Account.Id accountId;
-
- @Column(id = 3, notNull = false)
- protected String emailAddress;
-
- // Encoded version of the hashed and salted password, to be interpreted by the
- // {@link HashedPassword} class.
- @Column(id = 4, notNull = false)
- protected String password;
-
- /** <i>computed value</i> is this identity trusted by the site administrator? */
- protected boolean trusted;
-
- /** <i>computed value</i> can this identity be removed from the account? */
- protected boolean canDelete;
-
- protected AccountExternalId() {}
-
- /**
- * Create a new binding to an external identity.
- *
- * @param who the account this binds to.
- * @param k the binding key.
- */
- public AccountExternalId(final Account.Id who, final AccountExternalId.Key k) {
- accountId = who;
- key = k;
- }
-
- public AccountExternalId.Key getKey() {
- return key;
- }
-
- /** Get local id of this account, to link with in other entities */
- public Account.Id getAccountId() {
- return accountId;
- }
-
- public String getExternalId() {
- return key.externalId;
- }
-
- public String getEmailAddress() {
- return emailAddress;
- }
-
- public void setEmailAddress(final String e) {
- emailAddress = e;
- }
-
- public boolean isScheme(final String scheme) {
- final String id = getExternalId();
- return id != null && id.startsWith(scheme);
- }
-
- public String getSchemeRest() {
- String scheme = key.getScheme();
- return null != scheme ? getExternalId().substring(scheme.length() + 1) : null;
- }
-
- public void setPassword(String hashed) {
- password = hashed;
- }
-
- public String getPassword() {
- return password;
- }
-
- public boolean isTrusted() {
- return trusted;
- }
-
- public void setTrusted(final boolean t) {
- trusted = t;
- }
-
- public boolean canDelete() {
- return canDelete;
- }
-
- public void setCanDelete(final boolean t) {
- canDelete = t;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof AccountExternalId) {
- AccountExternalId extId = (AccountExternalId) o;
- return Objects.equals(key, extId.key)
- && Objects.equals(accountId, extId.accountId)
- && Objects.equals(emailAddress, extId.emailAddress)
- && Objects.equals(password, extId.password);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(key, accountId, emailAddress, password);
- }
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java
deleted file mode 100644
index e21faaf..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2008 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.server;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.PrimaryKey;
-import com.google.gwtorm.server.Query;
-import com.google.gwtorm.server.ResultSet;
-
-public interface AccountExternalIdAccess extends Access<AccountExternalId, AccountExternalId.Key> {
- @Override
- @PrimaryKey("key")
- AccountExternalId get(AccountExternalId.Key key) throws OrmException;
-
- @Query("WHERE accountId = ?")
- ResultSet<AccountExternalId> byAccount(Account.Id id) throws OrmException;
-
- @Query("WHERE emailAddress = ?")
- ResultSet<AccountExternalId> byEmailAddress(String email) throws OrmException;
-
- @Query
- ResultSet<AccountExternalId> all() throws OrmException;
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
index 49b9337..165578a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
@@ -50,8 +50,7 @@
@Relation(id = 6)
AccountAccess accounts();
- @Relation(id = 7)
- AccountExternalIdAccess accountExternalIds();
+ // Deleted @Relation(id = 7)
// Deleted @Relation(id = 8)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
index 8aeb3ad..3b22889 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
@@ -83,11 +83,6 @@
}
@Override
- public AccountExternalIdAccess accountExternalIds() {
- return delegate.accountExternalIds();
- }
-
- @Override
public AccountGroupAccess accountGroups() {
return delegate.accountGroups();
}
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
index 2fcf53c..871ed20 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
@@ -16,16 +16,6 @@
-- *********************************************************************
--- AccountExternalIdAccess
--- covers: byAccount
-CREATE INDEX account_external_ids_byAccount
-ON account_external_ids (account_id);
-
--- covers: byEmailAddress
-CREATE INDEX account_external_ids_byEmail
-ON account_external_ids (email_address);
-
--- *********************************************************************
-- AccountGroupMemberAccess
-- @PrimaryKey covers: byAccount
CREATE INDEX account_group_members_byGroup
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
index 3be3c26..c349241 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
@@ -19,18 +19,6 @@
-- *********************************************************************
--- AccountExternalIdAccess
--- covers: byAccount
-CREATE INDEX account_external_ids_byAccount
-ON account_external_ids (account_id)
-#
-
--- covers: byEmailAddress
-CREATE INDEX account_external_ids_byEmail
-ON account_external_ids (email_address)
-#
-
--- *********************************************************************
-- AccountGroupMemberAccess
-- @PrimaryKey covers: byAccount
CREATE INDEX account_group_members_byGroup
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index 641c613..da99fef 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -63,17 +63,6 @@
-- *********************************************************************
--- AccountExternalIdAccess
--- covers: byAccount
-CREATE INDEX account_external_ids_byAccount
-ON account_external_ids (account_id);
-
--- covers: byEmailAddress
-CREATE INDEX account_external_ids_byEmail
-ON account_external_ids (email_address);
-
-
--- *********************************************************************
-- AccountGroupMemberAccess
-- @PrimaryKey covers: byAccount
CREATE INDEX account_group_members_byGroup
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 6c342c1..249ec7e 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
@@ -16,6 +16,7 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -44,6 +45,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -223,8 +225,7 @@
public List<Comment> publishedByChange(ReviewDb db, ChangeNotes notes) throws OrmException {
if (!migration.readChanges()) {
- return sort(
- byCommentStatus(db.patchComments().byChange(notes.getChangeId()), Status.PUBLISHED));
+ return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), PUBLISHED));
}
notes.load();
@@ -403,6 +404,25 @@
.delete(toPatchLineComments(update.getId(), PatchLineComment.Status.DRAFT, comments));
}
+ public void deleteCommentByRewritingHistory(
+ ReviewDb db, ChangeUpdate update, Comment.Key commentKey, PatchSet.Id psId, String newMessage)
+ throws OrmException {
+ if (PrimaryStorage.of(update.getChange()).equals(PrimaryStorage.REVIEW_DB)) {
+ PatchLineComment.Key key =
+ new PatchLineComment.Key(new Patch.Key(psId, commentKey.filename), commentKey.uuid);
+ PatchLineComment patchLineComment = db.patchComments().get(key);
+
+ if (!patchLineComment.getStatus().equals(PUBLISHED)) {
+ throw new OrmException(String.format("comment %s is not published", key));
+ }
+
+ patchLineComment.setMessage(newMessage);
+ db.patchComments().upsert(Collections.singleton(patchLineComment));
+ }
+
+ update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage);
+ }
+
public void deleteAllDraftsFromAllUsers(Change.Id changeId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo)) {
@@ -533,7 +553,7 @@
ctx.getUser().updateRealAccountId(d::setRealAuthor);
setCommentRevId(d, patchListCache, notes.getChange(), ps);
}
- putComments(ctx.getDb(), ctx.getUpdate(psId), PatchLineComment.Status.PUBLISHED, drafts);
+ putComments(ctx.getDb(), ctx.getUpdate(psId), PUBLISHED, drafts);
}
private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index 08f879f..410dc5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -101,7 +101,7 @@
}
}
- // Generate a candidate list at 3x the size of what the user wants to see to
+ // Generate a candidate list at 2x the size of what the user wants to see to
// give the ranking algorithm a good set of candidates it can work with
private static final int CANDIDATE_LIST_MULTIPLIER = 2;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
index 950eac7..0120aed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
@@ -94,7 +94,7 @@
try (ReviewDb db = schema.open()) {
return Streams.concat(
Streams.stream(db.accounts().byPreferredEmail(email)).map(a -> a.getId()),
- externalIds.get().byEmail(db, email).stream().map(e -> e.accountId()))
+ externalIds.get().byEmail(email).stream().map(e -> e.accountId()))
.collect(toImmutableSet());
}
}
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 b2f1bae..866a423 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
@@ -208,7 +208,7 @@
return new AccountState(
account,
internalGroups,
- externalIds.byAccount(db, who),
+ externalIds.byAccount(who),
watchConfig.get().getProjectWatches(who));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 012ed5c..49a20fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -171,9 +171,7 @@
externalIdsUpdateFactory
.create()
.replace(
- db,
- extId,
- ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password()));
+ extId, ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password()));
}
if (!realm.allowsEdit(AccountFieldName.FULL_NAME)
@@ -235,7 +233,7 @@
AccountsUpdate accountsUpdate = accountsUpdateFactory.create();
accountsUpdate.upsert(db, account);
- ExternalId existingExtId = externalIds.get(db, extId.key());
+ ExternalId existingExtId = externalIds.get(extId.key());
if (existingExtId != null && !existingExtId.accountId().equals(extId.accountId())) {
// external ID is assigned to another account, do not overwrite
accountsUpdate.delete(db, account);
@@ -246,7 +244,7 @@
+ newId
+ "; external ID already in use.");
}
- externalIdsUpdateFactory.create().upsert(db, extId);
+ externalIdsUpdateFactory.create().upsert(extId);
} finally {
// If adding the account failed, it may be that it actually was the
// first account. So we reset the 'check for first account'-guard, as
@@ -279,7 +277,7 @@
//
IdentifiedUser user = userFactory.create(newId);
try {
- changeUserNameFactory.create(db, user, who.getUserName()).call();
+ changeUserNameFactory.create(user, who.getUserName()).call();
} catch (NameAlreadyUsedException e) {
String message =
"Cannot assign user name \""
@@ -347,7 +345,7 @@
// this is why the best we can do here is to fail early and cleanup
// the database
accountsUpdateFactory.create().delete(db, account);
- externalIdsUpdateFactory.create().delete(db, extId);
+ externalIdsUpdateFactory.create().delete(extId);
throw new AccountUserNameException(errorMessage, e);
}
}
@@ -373,8 +371,7 @@
} else {
externalIdsUpdateFactory
.create()
- .insert(
- db, ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress()));
+ .insert(ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress()));
if (who.getEmailAddress() != null) {
Account a = db.accounts().get(to);
@@ -409,22 +406,20 @@
*/
public AuthResult updateLink(Account.Id to, AuthRequest who)
throws OrmException, AccountException, IOException, ConfigInvalidException {
- try (ReviewDb db = schema.open()) {
- Collection<ExternalId> filteredExtIdsByScheme =
- externalIds.byAccount(db, to, who.getExternalIdKey().scheme());
+ Collection<ExternalId> filteredExtIdsByScheme =
+ externalIds.byAccount(to, who.getExternalIdKey().scheme());
- if (!filteredExtIdsByScheme.isEmpty()
- && (filteredExtIdsByScheme.size() > 1
- || !filteredExtIdsByScheme
- .stream()
- .filter(e -> e.key().equals(who.getExternalIdKey()))
- .findAny()
- .isPresent())) {
- externalIdsUpdateFactory.create().delete(db, filteredExtIdsByScheme);
- }
- byIdCache.evict(to);
- return link(to, who);
+ if (!filteredExtIdsByScheme.isEmpty()
+ && (filteredExtIdsByScheme.size() > 1
+ || !filteredExtIdsByScheme
+ .stream()
+ .filter(e -> e.key().equals(who.getExternalIdKey()))
+ .findAny()
+ .isPresent())) {
+ externalIdsUpdateFactory.create().delete(filteredExtIdsByScheme);
}
+ byIdCache.evict(to);
+ return link(to, who);
}
/**
@@ -445,7 +440,7 @@
throw new AccountException(
"Identity '" + who.getExternalIdKey().get() + "' in use by another account");
}
- externalIdsUpdateFactory.create().delete(db, extId);
+ externalIdsUpdateFactory.create().delete(extId);
if (who.getEmailAddress() != null) {
Account a = db.accounts().get(from);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
index 1a02ea1..d2a9610 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -19,7 +19,6 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -44,7 +43,7 @@
/** Generic factory to change any user's username. */
public interface Factory {
- ChangeUserName create(ReviewDb db, IdentifiedUser user, String newUsername);
+ ChangeUserName create(IdentifiedUser user, String newUsername);
}
private final AccountCache accountCache;
@@ -52,7 +51,6 @@
private final ExternalIds externalIds;
private final ExternalIdsUpdate.Server externalIdsUpdateFactory;
- private final ReviewDb db;
private final IdentifiedUser user;
private final String newUsername;
@@ -62,14 +60,12 @@
SshKeyCache sshKeyCache,
ExternalIds externalIds,
ExternalIdsUpdate.Server externalIdsUpdateFactory,
- @Assisted ReviewDb db,
@Assisted IdentifiedUser user,
@Nullable @Assisted String newUsername) {
this.accountCache = accountCache;
this.sshKeyCache = sshKeyCache;
this.externalIds = externalIds;
this.externalIdsUpdateFactory = externalIdsUpdateFactory;
- this.db = db;
this.user = user;
this.newUsername = newUsername;
}
@@ -78,7 +74,7 @@
public VoidResult call()
throws OrmException, NameAlreadyUsedException, InvalidUserNameException, IOException,
ConfigInvalidException {
- Collection<ExternalId> old = externalIds.byAccount(db, user.getAccountId(), SCHEME_USERNAME);
+ Collection<ExternalId> old = externalIds.byAccount(user.getAccountId(), SCHEME_USERNAME);
if (!old.isEmpty()) {
throw new IllegalStateException(USERNAME_CANNOT_BE_CHANGED);
}
@@ -97,11 +93,11 @@
password = i.password();
}
}
- externalIdsUpdate.insert(db, ExternalId.create(key, user.getAccountId(), null, password));
+ externalIdsUpdate.insert(ExternalId.create(key, user.getAccountId(), null, password));
} catch (OrmDuplicateKeyException dupeErr) {
// If we are using this identity, don't report the exception.
//
- ExternalId other = externalIds.get(db, key);
+ ExternalId other = externalIds.get(key);
if (other != null && other.accountId().equals(user.getAccountId())) {
return VoidResult.INSTANCE;
}
@@ -114,7 +110,7 @@
// If we have any older user names, remove them.
//
- externalIdsUpdate.delete(db, old);
+ externalIdsUpdate.delete(old);
for (ExternalId extId : old) {
sshKeyCache.evict(extId.key().id());
accountCache.evictByUsername(extId.key().id());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 2cfd716..5ea5e96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -137,11 +137,11 @@
Account.Id id = new Account.Id(db.nextAccountId());
ExternalId extUser = ExternalId.createUsername(username, id, input.httpPassword);
- if (externalIds.get(db, extUser.key()) != null) {
+ if (externalIds.get(extUser.key()) != null) {
throw new ResourceConflictException("username '" + username + "' already exists");
}
if (input.email != null) {
- if (externalIds.get(db, ExternalId.Key.create(SCHEME_MAILTO, input.email)) != null) {
+ if (externalIds.get(ExternalId.Key.create(SCHEME_MAILTO, input.email)) != null) {
throw new UnprocessableEntityException("email '" + input.email + "' already exists");
}
if (!validator.isValid(input.email)) {
@@ -157,17 +157,17 @@
ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
try {
- externalIdsUpdate.insert(db, extIds);
+ externalIdsUpdate.insert(extIds);
} catch (OrmDuplicateKeyException duplicateKey) {
throw new ResourceConflictException("username '" + username + "' already exists");
}
if (input.email != null) {
try {
- externalIdsUpdate.insert(db, ExternalId.createEmail(id, input.email));
+ externalIdsUpdate.insert(ExternalId.createEmail(id, input.email));
} catch (OrmDuplicateKeyException duplicateKey) {
try {
- externalIdsUpdate.delete(db, extUser);
+ externalIdsUpdate.delete(extUser);
} catch (IOException | ConfigInvalidException cleanupError) {
// Ignored
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index b4e2bdb..ca56eb1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -23,7 +23,6 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.DeleteEmail.Input;
@@ -47,7 +46,6 @@
private final Provider<CurrentUser> self;
private final Realm realm;
private final PermissionBackend permissionBackend;
- private final Provider<ReviewDb> dbProvider;
private final AccountManager accountManager;
private final ExternalIds externalIds;
@@ -56,13 +54,11 @@
Provider<CurrentUser> self,
Realm realm,
PermissionBackend permissionBackend,
- Provider<ReviewDb> dbProvider,
AccountManager accountManager,
ExternalIds externalIds) {
this.self = self;
this.realm = realm;
this.permissionBackend = permissionBackend;
- this.dbProvider = dbProvider;
this.accountManager = accountManager;
this.externalIds = externalIds;
}
@@ -87,7 +83,7 @@
Set<ExternalId> extIds =
externalIds
- .byAccount(dbProvider.get(), user.getAccountId())
+ .byAccount(user.getAccountId())
.stream()
.filter(e -> email.equals(e.email()))
.collect(toSet());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteExternalIds.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteExternalIds.java
index 27a0038..78eb8a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteExternalIds.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteExternalIds.java
@@ -24,7 +24,6 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -41,18 +40,13 @@
private final AccountManager accountManager;
private final ExternalIds externalIds;
private final Provider<CurrentUser> self;
- private final Provider<ReviewDb> dbProvider;
@Inject
DeleteExternalIds(
- AccountManager accountManager,
- ExternalIds externalIds,
- Provider<CurrentUser> self,
- Provider<ReviewDb> dbProvider) {
+ AccountManager accountManager, ExternalIds externalIds, Provider<CurrentUser> self) {
this.accountManager = accountManager;
this.externalIds = externalIds;
this.self = self;
- this.dbProvider = dbProvider;
}
@Override
@@ -68,7 +62,7 @@
Map<ExternalId.Key, ExternalId> externalIdMap =
externalIds
- .byAccount(dbProvider.get(), resource.getUser().getAccountId())
+ .byAccount(resource.getUser().getAccountId())
.stream()
.collect(toMap(i -> i.key(), i -> i));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetExternalIds.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetExternalIds.java
index 12de82c..46c1dd8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetExternalIds.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetExternalIds.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -38,18 +37,12 @@
@Singleton
public class GetExternalIds implements RestReadView<AccountResource> {
- private final Provider<ReviewDb> db;
private final ExternalIds externalIds;
private final Provider<CurrentUser> self;
private final AuthConfig authConfig;
@Inject
- GetExternalIds(
- Provider<ReviewDb> db,
- ExternalIds externalIds,
- Provider<CurrentUser> self,
- AuthConfig authConfig) {
- this.db = db;
+ GetExternalIds(ExternalIds externalIds, Provider<CurrentUser> self, AuthConfig authConfig) {
this.externalIds = externalIds;
this.self = self;
this.authConfig = authConfig;
@@ -62,7 +55,7 @@
throw new AuthException("not allowed to get external IDs");
}
- Collection<ExternalId> ids = externalIds.byAccount(db.get(), resource.getUser().getAccountId());
+ Collection<ExternalId> ids = externalIds.byAccount(resource.getUser().getAccountId());
if (ids.isEmpty()) {
return ImmutableList.of();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index c06e5a3..395f078 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.PutHttpPassword.Input;
@@ -59,7 +58,6 @@
}
private final Provider<CurrentUser> self;
- private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final AccountCache accountCache;
private final ExternalIds externalIds;
@@ -68,13 +66,11 @@
@Inject
PutHttpPassword(
Provider<CurrentUser> self,
- Provider<ReviewDb> dbProvider,
PermissionBackend permissionBackend,
AccountCache accountCache,
ExternalIds externalIds,
ExternalIdsUpdate.User externalIdsUpdate) {
this.self = self;
- this.dbProvider = dbProvider;
this.permissionBackend = permissionBackend;
this.accountCache = accountCache;
this.externalIds = externalIds;
@@ -114,15 +110,13 @@
throw new ResourceConflictException("username must be set");
}
- ExternalId extId =
- externalIds.get(
- dbProvider.get(), ExternalId.Key.create(SCHEME_USERNAME, user.getUserName()));
+ ExternalId extId = externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, user.getUserName()));
if (extId == null) {
throw new ResourceNotFoundException();
}
ExternalId newExtId =
ExternalId.createWithPassword(extId.key(), extId.accountId(), extId.email(), newPassword);
- externalIdsUpdate.create().upsert(dbProvider.get(), newExtId);
+ externalIdsUpdate.create().upsert(newExtId);
accountCache.evict(user.getAccountId());
return Strings.isNullOrEmpty(newPassword) ? Response.<String>none() : Response.ok(newPassword);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
index 57bff65..a73bdd9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.PutUsername.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -45,20 +44,17 @@
private final ChangeUserName.Factory changeUserNameFactory;
private final PermissionBackend permissionBackend;
private final Realm realm;
- private final Provider<ReviewDb> db;
@Inject
PutUsername(
Provider<CurrentUser> self,
ChangeUserName.Factory changeUserNameFactory,
PermissionBackend permissionBackend,
- Realm realm,
- Provider<ReviewDb> db) {
+ Realm realm) {
this.self = self;
this.changeUserNameFactory = changeUserNameFactory;
this.permissionBackend = permissionBackend;
this.realm = realm;
- this.db = db;
}
@Override
@@ -79,7 +75,7 @@
}
try {
- changeUserNameFactory.create(db.get(), rsrc.getUser(), input.username).call();
+ changeUserNameFactory.create(rsrc.getUser(), input.username).call();
} catch (IllegalStateException e) {
if (ChangeUserName.USERNAME_CANNOT_BE_CHANGED.equals(e.getMessage())) {
throw new MethodNotAllowedException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
index b4fef67..4aabf59 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -16,20 +16,16 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.stream.Collectors.toSet;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.server.account.HashedPassword;
import java.io.Serializable;
-import java.util.Collection;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
@@ -75,10 +71,6 @@
return new AutoValue_ExternalId_Key(Strings.emptyToNull(scheme), id);
}
- public static ExternalId.Key from(AccountExternalId.Key externalIdKey) {
- return parse(externalIdKey.get());
- }
-
/**
* Parses an external ID key from a string in the format "scheme:id" or "id".
*
@@ -92,11 +84,6 @@
return create(externalId.substring(0, c), externalId.substring(c + 1));
}
- public static Set<AccountExternalId.Key> toAccountExternalIdKeys(
- Collection<ExternalId.Key> extIdKeys) {
- return extIdKeys.stream().map(k -> k.asAccountExternalIdKey()).collect(toSet());
- }
-
public abstract @Nullable String scheme();
public abstract String id();
@@ -105,13 +92,6 @@
return scheme.equals(scheme());
}
- public AccountExternalId.Key asAccountExternalIdKey() {
- if (scheme() != null) {
- return new AccountExternalId.Key(scheme(), id());
- }
- return new AccountExternalId.Key(id());
- }
-
/**
* Returns the SHA1 of the external ID that is used as note ID in the refs/meta/external-ids
* notes branch.
@@ -281,29 +261,6 @@
String.format("Invalid external ID config for note '%s': %s", noteId, message));
}
- public static ExternalId from(AccountExternalId externalId) {
- if (externalId == null) {
- return null;
- }
-
- return new AutoValue_ExternalId(
- ExternalId.Key.parse(externalId.getExternalId()),
- externalId.getAccountId(),
- Strings.emptyToNull(externalId.getEmailAddress()),
- Strings.emptyToNull(externalId.getPassword()));
- }
-
- public static Set<ExternalId> from(Collection<AccountExternalId> externalIds) {
- if (externalIds == null) {
- return ImmutableSet.of();
- }
- return externalIds.stream().map(ExternalId::from).collect(toSet());
- }
-
- public static Set<AccountExternalId> toAccountExternalIds(Collection<ExternalId> extIds) {
- return extIds.stream().map(e -> e.asAccountExternalId()).collect(toSet());
- }
-
public abstract Key key();
public abstract Account.Id accountId();
@@ -316,13 +273,6 @@
return key().isScheme(scheme);
}
- public AccountExternalId asAccountExternalId() {
- AccountExternalId extId = new AccountExternalId(accountId(), key().asAccountExternalIdKey());
- extId.setEmailAddress(email());
- extId.setPassword(password());
- return extId;
- }
-
/**
* Exports this external ID as Git config file text.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index e25c36f..ead2c1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -24,18 +24,14 @@
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -46,7 +42,7 @@
import org.slf4j.LoggerFactory;
/**
- * Class to read external IDs from ReviewDb or NoteDb.
+ * Class to read external IDs from NoteDb.
*
* <p>In NoteDb external IDs are stored in the All-Users repository in a Git Notes branch called
* refs/meta/external-ids where the sha1 of the external ID is used as note name. Each note content
@@ -78,7 +74,6 @@
return NoteMap.newEmptyMap();
}
- private final boolean readFromGit;
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private boolean failOnLoad = false;
@@ -86,11 +81,7 @@
@Inject
ExternalIdReader(
- @GerritServerConfig Config cfg,
- GitRepositoryManager repoManager,
- AllUsersName allUsersName,
- MetricMaker metricMaker) {
- this.readFromGit = cfg.getBoolean("user", null, "readExternalIdsFromGit", false);
+ GitRepositoryManager repoManager, AllUsersName allUsersName, MetricMaker metricMaker) {
this.repoManager = repoManager;
this.allUsersName = allUsersName;
this.readAllLatency =
@@ -106,10 +97,6 @@
this.failOnLoad = failOnLoad;
}
- boolean readFromGit() {
- return readFromGit;
- }
-
ObjectId readRevision() throws IOException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
return readRevision(repo);
@@ -117,16 +104,12 @@
}
/** Reads and returns all external IDs. */
- Set<ExternalId> all(ReviewDb db) throws IOException, OrmException {
+ Set<ExternalId> all() throws IOException {
checkReadEnabled();
- if (readFromGit) {
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- return all(repo, readRevision(repo));
- }
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ return all(repo, readRevision(repo));
}
-
- return ExternalId.from(db.accountExternalIds().all().toList());
}
/**
@@ -166,22 +149,18 @@
/** Reads and returns the specified external ID. */
@Nullable
- ExternalId get(ReviewDb db, ExternalId.Key key)
- throws IOException, ConfigInvalidException, OrmException {
+ ExternalId get(ExternalId.Key key) throws IOException, ConfigInvalidException {
checkReadEnabled();
- if (readFromGit) {
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo)) {
- ObjectId rev = readRevision(repo);
- if (rev.equals(ObjectId.zeroId())) {
- return null;
- }
-
- return parse(key, rw, rev);
+ try (Repository repo = repoManager.openRepository(allUsersName);
+ RevWalk rw = new RevWalk(repo)) {
+ ObjectId rev = readRevision(repo);
+ if (rev.equals(ObjectId.zeroId())) {
+ return null;
}
+
+ return parse(key, rw, rev);
}
- return ExternalId.from(db.accountExternalIds().get(key.asAccountExternalIdKey()));
}
/** Reads and returns the specified external ID from the given revision. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIds.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIds.java
index b77fed8..5003a35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIds.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIds.java
@@ -18,8 +18,6 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -44,8 +42,8 @@
}
/** Returns all external IDs. */
- public Set<ExternalId> all(ReviewDb db) throws IOException, OrmException {
- return externalIdReader.all(db);
+ public Set<ExternalId> all() throws IOException {
+ return externalIdReader.all();
}
/** Returns all external IDs from the specified revision of the refs/meta/external-ids branch. */
@@ -55,9 +53,8 @@
/** Returns the specified external ID. */
@Nullable
- public ExternalId get(ReviewDb db, ExternalId.Key key)
- throws IOException, ConfigInvalidException, OrmException {
- return externalIdReader.get(db, key);
+ public ExternalId get(ExternalId.Key key) throws IOException, ConfigInvalidException {
+ return externalIdReader.get(key);
}
/** Returns the specified external ID from the given revision. */
@@ -68,26 +65,16 @@
}
/** Returns the external IDs of the specified account. */
- public Set<ExternalId> byAccount(ReviewDb db, Account.Id accountId)
- throws IOException, OrmException {
- if (externalIdReader.readFromGit()) {
- return externalIdCache.byAccount(accountId);
- }
-
- return ExternalId.from(db.accountExternalIds().byAccount(accountId).toList());
+ public Set<ExternalId> byAccount(Account.Id accountId) throws IOException {
+ return externalIdCache.byAccount(accountId);
}
/** Returns the external IDs of the specified account that have the given scheme. */
- public Set<ExternalId> byAccount(ReviewDb db, Account.Id accountId, String scheme)
- throws IOException, OrmException {
- return byAccount(db, accountId).stream().filter(e -> e.key().isScheme(scheme)).collect(toSet());
+ public Set<ExternalId> byAccount(Account.Id accountId, String scheme) throws IOException {
+ return byAccount(accountId).stream().filter(e -> e.key().isScheme(scheme)).collect(toSet());
}
- public Set<ExternalId> byEmail(ReviewDb db, String email) throws IOException, OrmException {
- if (externalIdReader.readFromGit()) {
- return externalIdCache.byEmail(email);
- }
-
- return ExternalId.from(db.accountExternalIds().byEmailAddress(email).toList());
+ public Set<ExternalId> byEmail(String email) throws IOException {
+ return externalIdCache.byEmail(email);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
index 492866d..e35b0c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
@@ -14,10 +14,7 @@
package com.google.gerrit.server.account.externalids;
-import static com.google.gerrit.server.account.externalids.ExternalId.toAccountExternalIds;
-
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -39,8 +36,8 @@
*
* <p>For NoteDb all updates will result in a single commit to the refs/meta/external-ids branch.
* This means callers can prepare many updates by invoking {@link #replace(ExternalId, ExternalId)}
- * multiple times and when {@link ExternalIdsBatchUpdate#commit(ReviewDb, String)} is invoked a
- * single NoteDb commit is created that contains all the prepared updates.
+ * multiple times and when {@link ExternalIdsBatchUpdate#commit(String)} is invoked a single NoteDb
+ * commit is created that contains all the prepared updates.
*/
public class ExternalIdsBatchUpdate {
private final GitRepositoryManager repoManager;
@@ -65,7 +62,7 @@
/**
* Adds an external ID replacement to the batch.
*
- * <p>The actual replacement is only done when {@link #commit(ReviewDb, String)} is invoked.
+ * <p>The actual replacement is only done when {@link #commit(String)} is invoked.
*/
public void replace(ExternalId extIdToDelete, ExternalId extIdToAdd) {
ExternalIdsUpdate.checkSameAccount(ImmutableSet.of(extIdToDelete, extIdToAdd));
@@ -85,15 +82,12 @@
*
* <p>For NoteDb a single commit is created that contains all the external ID updates.
*/
- public void commit(ReviewDb db, String commitMessage)
+ public void commit(String commitMessage)
throws IOException, OrmException, ConfigInvalidException {
if (toDelete.isEmpty() && toAdd.isEmpty()) {
return;
}
- db.accountExternalIds().delete(toAccountExternalIds(toDelete));
- db.accountExternalIds().insert(toAccountExternalIds(toAdd));
-
try (Repository repo = repoManager.openRepository(allUsersName);
RevWalk rw = new RevWalk(repo);
ObjectInserter ins = repo.newObjectInserter()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
index 003928f..a1d6eeb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
@@ -16,8 +16,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.account.externalids.ExternalId.Key.toAccountExternalIdKeys;
-import static com.google.gerrit.server.account.externalids.ExternalId.toAccountExternalIds;
import static com.google.gerrit.server.account.externalids.ExternalIdReader.MAX_NOTE_SZ;
import static com.google.gerrit.server.account.externalids.ExternalIdReader.readNoteMap;
import static com.google.gerrit.server.account.externalids.ExternalIdReader.readRevision;
@@ -42,7 +40,6 @@
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
@@ -252,9 +249,8 @@
*
* <p>If the external ID already exists, the insert fails with {@link OrmDuplicateKeyException}.
*/
- public void insert(ReviewDb db, ExternalId extId)
- throws IOException, ConfigInvalidException, OrmException {
- insert(db, Collections.singleton(extId));
+ public void insert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
+ insert(Collections.singleton(extId));
}
/**
@@ -263,10 +259,8 @@
* <p>If any of the external ID already exists, the insert fails with {@link
* OrmDuplicateKeyException}.
*/
- public void insert(ReviewDb db, Collection<ExternalId> extIds)
+ public void insert(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().insert(toAccountExternalIds(extIds));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -282,9 +276,8 @@
*
* <p>If the external ID already exists, it is overwritten, otherwise it is inserted.
*/
- public void upsert(ReviewDb db, ExternalId extId)
- throws IOException, ConfigInvalidException, OrmException {
- upsert(db, Collections.singleton(extId));
+ public void upsert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
+ upsert(Collections.singleton(extId));
}
/**
@@ -292,10 +285,8 @@
*
* <p>If any of the external IDs already exists, it is overwritten. New external IDs are inserted.
*/
- public void upsert(ReviewDb db, Collection<ExternalId> extIds)
+ public void upsert(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().upsert(toAccountExternalIds(extIds));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -312,9 +303,8 @@
* @throws IllegalStateException is thrown if there is an existing external ID that has the same
* key, but otherwise doesn't match the specified external ID.
*/
- public void delete(ReviewDb db, ExternalId extId)
- throws IOException, ConfigInvalidException, OrmException {
- delete(db, Collections.singleton(extId));
+ public void delete(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
+ delete(Collections.singleton(extId));
}
/**
@@ -324,10 +314,8 @@
* key as any of the external IDs that should be deleted, but otherwise doesn't match the that
* external ID.
*/
- public void delete(ReviewDb db, Collection<ExternalId> extIds)
+ public void delete(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().delete(toAccountExternalIds(extIds));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -344,9 +332,9 @@
* @throws IllegalStateException is thrown if the external ID does not belong to the specified
* account.
*/
- public void delete(ReviewDb db, Account.Id accountId, ExternalId.Key extIdKey)
+ public void delete(Account.Id accountId, ExternalId.Key extIdKey)
throws IOException, ConfigInvalidException, OrmException {
- delete(db, accountId, Collections.singleton(extIdKey));
+ delete(accountId, Collections.singleton(extIdKey));
}
/**
@@ -355,10 +343,8 @@
* @throws IllegalStateException is thrown if any of the external IDs does not belong to the
* specified account.
*/
- public void delete(ReviewDb db, Account.Id accountId, Collection<ExternalId.Key> extIdKeys)
+ public void delete(Account.Id accountId, Collection<ExternalId.Key> extIdKeys)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().deleteKeys(toAccountExternalIdKeys(extIdKeys));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -374,10 +360,8 @@
*
* <p>The external IDs are deleted regardless of which account they belong to.
*/
- public void deleteByKeys(ReviewDb db, Collection<ExternalId.Key> extIdKeys)
+ public void deleteByKeys(Collection<ExternalId.Key> extIdKeys)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().deleteKeys(toAccountExternalIdKeys(extIdKeys));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -389,9 +373,9 @@
}
/** Deletes all external IDs of the specified account. */
- public void deleteAll(ReviewDb db, Account.Id accountId)
+ public void deleteAll(Account.Id accountId)
throws IOException, ConfigInvalidException, OrmException {
- delete(db, externalIds.byAccount(db, accountId));
+ delete(externalIds.byAccount(accountId));
}
/**
@@ -406,16 +390,10 @@
* the specified account.
*/
public void replace(
- ReviewDb db,
- Account.Id accountId,
- Collection<ExternalId.Key> toDelete,
- Collection<ExternalId> toAdd)
+ Account.Id accountId, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
checkSameAccount(toAdd, accountId);
- db.accountExternalIds().deleteKeys(toAccountExternalIdKeys(toDelete));
- db.accountExternalIds().insert(toAccountExternalIds(toAdd));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -440,12 +418,8 @@
*
* <p>The external IDs are replaced regardless of which account they belong to.
*/
- public void replaceByKeys(
- ReviewDb db, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
+ public void replaceByKeys(Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
- db.accountExternalIds().deleteKeys(toAccountExternalIdKeys(toDelete));
- db.accountExternalIds().insert(toAccountExternalIds(toAdd));
-
RefsMetaExternalIdsUpdate u =
updateNoteMap(
o -> {
@@ -466,9 +440,9 @@
* @throws IllegalStateException is thrown if the specified external IDs belong to different
* accounts.
*/
- public void replace(ReviewDb db, ExternalId toDelete, ExternalId toAdd)
+ public void replace(ExternalId toDelete, ExternalId toAdd)
throws IOException, ConfigInvalidException, OrmException {
- replace(db, Collections.singleton(toDelete), Collections.singleton(toAdd));
+ replace(Collections.singleton(toDelete), Collections.singleton(toAdd));
}
/**
@@ -482,7 +456,7 @@
* @throws IllegalStateException is thrown if the specified external IDs belong to different
* accounts.
*/
- public void replace(ReviewDb db, Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
+ public void replace(Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
Account.Id accountId = checkSameAccount(Iterables.concat(toDelete, toAdd));
if (accountId == null) {
@@ -490,7 +464,7 @@
return;
}
- replace(db, accountId, toDelete.stream().map(e -> e.key()).collect(toSet()), toAdd);
+ replace(accountId, toDelete.stream().map(e -> e.key()).collect(toSet()), toAdd);
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 5338e89..f4ea3b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
@@ -66,16 +67,17 @@
import com.google.gerrit.server.change.Move;
import com.google.gerrit.server.change.Mute;
import com.google.gerrit.server.change.PostHashtags;
+import com.google.gerrit.server.change.PostPrivate;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.PutAssignee;
-import com.google.gerrit.server.change.PutPrivate;
import com.google.gerrit.server.change.PutTopic;
import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Reviewers;
import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.change.SetPrivateOp;
import com.google.gerrit.server.change.SetReadyForReview;
import com.google.gerrit.server.change.SetWorkInProgress;
import com.google.gerrit.server.change.SubmittedTogether;
@@ -129,7 +131,7 @@
private final Check check;
private final Index index;
private final Move move;
- private final PutPrivate putPrivate;
+ private final PostPrivate postPrivate;
private final DeletePrivate deletePrivate;
private final Ignore ignore;
private final Unignore unignore;
@@ -172,7 +174,7 @@
Check check,
Index index,
Move move,
- PutPrivate putPrivate,
+ PostPrivate postPrivate,
DeletePrivate deletePrivate,
Ignore ignore,
Unignore unignore,
@@ -213,7 +215,7 @@
this.check = check;
this.index = index;
this.move = move;
- this.putPrivate = putPrivate;
+ this.postPrivate = postPrivate;
this.deletePrivate = deletePrivate;
this.ignore = ignore;
this.unignore = unignore;
@@ -302,12 +304,13 @@
}
@Override
- public void setPrivate(boolean value) throws RestApiException {
+ public void setPrivate(boolean value, @Nullable String message) throws RestApiException {
try {
+ SetPrivateOp.Input input = new SetPrivateOp.Input(message);
if (value) {
- putPrivate.apply(change, null);
+ postPrivate.apply(change, input);
} else {
- deletePrivate.apply(change, null);
+ deletePrivate.apply(change, input);
}
} catch (Exception e) {
throw asRestApiException("Cannot change private status", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
index 243833a..6a2501e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
@@ -17,9 +17,11 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import com.google.gerrit.extensions.api.changes.CommentApi;
+import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.change.CommentResource;
+import com.google.gerrit.server.change.DeleteComment;
import com.google.gerrit.server.change.GetComment;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -30,11 +32,14 @@
}
private final GetComment getComment;
+ private final DeleteComment deleteComment;
private final CommentResource comment;
@Inject
- CommentApiImpl(GetComment getComment, @Assisted CommentResource comment) {
+ CommentApiImpl(
+ GetComment getComment, DeleteComment deleteComment, @Assisted CommentResource comment) {
this.getComment = getComment;
+ this.deleteComment = deleteComment;
this.comment = comment;
}
@@ -46,4 +51,13 @@
throw asRestApiException("Cannot retrieve comment", e);
}
}
+
+ @Override
+ public CommentInfo delete(DeleteCommentInput input) throws RestApiException {
+ try {
+ return deleteComment.apply(comment, input);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot delete comment", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
index 2daf1dc..eada51b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
@@ -16,9 +16,11 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.change.DeleteDraftComment;
import com.google.gerrit.server.change.DraftCommentResource;
@@ -75,4 +77,9 @@
throw asRestApiException("Cannot delete draft", e);
}
}
+
+ @Override
+ public CommentInfo delete(DeleteCommentInput input) {
+ throw new NotImplementedException();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 9bcf3d6..4685dc0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -25,7 +25,6 @@
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AbstractRealm;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AuthRequest;
@@ -36,7 +35,6 @@
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
@@ -319,22 +317,17 @@
}
static class UserLoader extends CacheLoader<String, Optional<Account.Id>> {
- private final SchemaFactory<ReviewDb> schema;
private final ExternalIds externalIds;
@Inject
- UserLoader(SchemaFactory<ReviewDb> schema, ExternalIds externalIds) {
- this.schema = schema;
+ UserLoader(ExternalIds externalIds) {
this.externalIds = externalIds;
}
@Override
public Optional<Account.Id> load(String username) throws Exception {
- try (ReviewDb db = schema.open()) {
- return Optional.ofNullable(
- externalIds.get(db, ExternalId.Key.create(SCHEME_GERRIT, username)))
- .map(ExternalId::accountId);
- }
+ return Optional.ofNullable(externalIds.get(ExternalId.Key.create(SCHEME_GERRIT, username)))
+ .map(ExternalId::accountId);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
index 40c8515..f7fc576 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
@@ -48,4 +48,8 @@
Account.Id getAuthorId() {
return comment.author.getId();
}
+
+ RevisionResource getRevisionResource() {
+ return rev;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 3c404d6..599ce5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -180,7 +180,7 @@
}
RefControl refControl = rsrc.getControl().controlForRef(refName);
- if (!refControl.canUpload() || !refControl.canRead()) {
+ if (!refControl.canUpload() || !refControl.isVisible()) {
throw new AuthException("cannot upload review");
}
@@ -194,11 +194,11 @@
if (input.baseChange != null) {
List<ChangeControl> ctls = changeFinder.find(input.baseChange, rsrc.getControl().getUser());
if (ctls.size() != 1) {
- throw new InvalidChangeOperationException("Base change not found: " + input.baseChange);
+ throw new UnprocessableEntityException("Base change not found: " + input.baseChange);
}
ChangeControl ctl = Iterables.getOnlyElement(ctls);
if (!ctl.isVisible(db.get())) {
- throw new InvalidChangeOperationException("Base change not found: " + input.baseChange);
+ throw new UnprocessableEntityException("Base change not found: " + input.baseChange);
}
PatchSet ps = psUtil.current(db.get(), ctl.getNotes());
parentCommit = ObjectId.fromString(ps.getRevision().get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteComment.java
new file mode 100644
index 0000000..b0b222b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteComment.java
@@ -0,0 +1,145 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommentsUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class DeleteComment implements RestModifyView<CommentResource, DeleteCommentInput> {
+
+ private final Provider<CurrentUser> userProvider;
+ private final Provider<ReviewDb> dbProvider;
+ private final PermissionBackend permissionBackend;
+ private final BatchUpdate.Factory batchUpdateFactory;
+ private final CommentsUtil commentsUtil;
+ private final PatchSetUtil psUtil;
+ private final BatchUpdate.Factory updateFactory;
+ private final PatchListCache patchListCache;
+ private final Provider<CommentJson> commentJson;
+ private final ChangeNotes.Factory notesFactory;
+
+ @Inject
+ public DeleteComment(
+ Provider<CurrentUser> userProvider,
+ Provider<ReviewDb> dbProvider,
+ PermissionBackend permissionBackend,
+ BatchUpdate.Factory batchUpdateFactory,
+ CommentsUtil commentsUtil,
+ PatchSetUtil psUtil,
+ BatchUpdate.Factory updateFactory,
+ PatchListCache patchListCache,
+ Provider<CommentJson> commentJson,
+ ChangeNotes.Factory notesFactory) {
+ this.userProvider = userProvider;
+ this.dbProvider = dbProvider;
+ this.permissionBackend = permissionBackend;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.commentsUtil = commentsUtil;
+ this.psUtil = psUtil;
+ this.updateFactory = updateFactory;
+ this.patchListCache = patchListCache;
+ this.commentJson = commentJson;
+ this.notesFactory = notesFactory;
+ }
+
+ @Override
+ public CommentInfo apply(CommentResource rsrc, DeleteCommentInput input)
+ throws RestApiException, IOException, ConfigInvalidException, OrmException,
+ PermissionBackendException, UpdateException {
+ CurrentUser user = userProvider.get();
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+
+ String newMessage = getCommentNewMessage(user.asIdentifiedUser().getName(), input.reason);
+ DeleteCommentOp deleteCommentOp = new DeleteCommentOp(rsrc, newMessage);
+ try (BatchUpdate batchUpdate =
+ batchUpdateFactory.create(
+ dbProvider.get(), rsrc.getRevisionResource().getProject(), user, TimeUtil.nowTs())) {
+ batchUpdate.addOp(rsrc.getRevisionResource().getChange().getId(), deleteCommentOp).execute();
+ }
+
+ ChangeNotes updatedNotes =
+ notesFactory.createChecked(rsrc.getRevisionResource().getChange().getId());
+ List<Comment> changeComments = commentsUtil.publishedByChange(dbProvider.get(), updatedNotes);
+ Optional<Comment> updatedComment =
+ changeComments.stream().filter(c -> c.key.equals(rsrc.getComment().key)).findFirst();
+ if (!updatedComment.isPresent()) {
+ // This should not happen as this endpoint should not remove the whole comment.
+ throw new ResourceNotFoundException("comment not found: " + rsrc.getComment().key);
+ }
+
+ return commentJson.get().newCommentFormatter().format(updatedComment.get());
+ }
+
+ private static String getCommentNewMessage(String name, String reason) {
+ StringBuilder stringBuilder = new StringBuilder("Comment removed by: ").append(name);
+ if (!Strings.isNullOrEmpty(reason)) {
+ stringBuilder.append("; Reason: ").append(reason);
+ }
+ return stringBuilder.toString();
+ }
+
+ private class DeleteCommentOp implements BatchUpdateOp {
+ private final CommentResource rsrc;
+ private final String newMessage;
+
+ DeleteCommentOp(CommentResource rsrc, String newMessage) {
+ this.rsrc = rsrc;
+ this.newMessage = newMessage;
+ }
+
+ @Override
+ public boolean updateChange(ChangeContext ctx)
+ throws ResourceConflictException, OrmException, ResourceNotFoundException {
+ PatchSet.Id psId = ctx.getChange().currentPatchSetId();
+ commentsUtil.deleteCommentByRewritingHistory(
+ ctx.getDb(),
+ ctx.getUpdate(psId),
+ rsrc.getComment().key,
+ rsrc.getPatchSet().getId(),
+ newMessage);
+ return true;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
index a951d66..7819a29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
@@ -33,10 +33,7 @@
@Singleton
public class DeletePrivate
- extends RetryingRestModifyView<ChangeResource, DeletePrivate.Input, Response<String>>
- implements UiAction<ChangeResource> {
- public static class Input {}
-
+ extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, Response<String>> {
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
@@ -49,7 +46,7 @@
@Override
protected Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource rsrc, DeletePrivate.Input input)
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
throws RestApiException, UpdateException {
if (!rsrc.isUserOwner()) {
throw new AuthException("not allowed to unmark private");
@@ -60,7 +57,7 @@
}
ChangeControl control = rsrc.getControl();
- SetPrivateOp op = new SetPrivateOp(cmUtil, false);
+ SetPrivateOp op = new SetPrivateOp(cmUtil, false, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(),
@@ -73,11 +70,20 @@
return Response.none();
}
- @Override
- public Description getDescription(ChangeResource rsrc) {
- return new UiAction.Description()
- .setLabel("Unmark private")
- .setTitle("Unmark change as private")
- .setVisible(rsrc.getChange().isPrivate() && rsrc.isUserOwner());
+ public static class DeletePrivateByPost extends DeletePrivate
+ implements UiAction<ChangeResource> {
+ @Inject
+ DeletePrivateByPost(
+ Provider<ReviewDb> dbProvider, RetryHelper retryHelper, ChangeMessagesUtil cmUtil) {
+ super(dbProvider, retryHelper, cmUtil);
+ }
+
+ @Override
+ public Description getDescription(ChangeResource rsrc) {
+ return new UiAction.Description()
+ .setLabel("Unmark private")
+ .setTitle("Unmark change as private")
+ .setVisible(rsrc.getChange().isPrivate() && rsrc.isUserOwner());
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
index e812002..01401b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -75,7 +75,7 @@
* @param revstr An {@code ObjectId} specifying the commit.
* @param path A string specifying the filepath.
* @param parent A 1-based parent index to get the content from instead. Null if the content
- * should be obtained from {@param revstr} instead.
+ * should be obtained from {@code revstr} instead.
* @return Content of the file as {@code BinaryResult}.
* @throws ResourceNotFoundException
* @throws IOException
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index ebc9971..1a0d118 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -252,11 +253,18 @@
ObjectReader reader = git.newObjectReader();
RevWalk rw = new RevWalk(reader);
TreeWalk tw = new TreeWalk(reader)) {
- PatchList oldList =
- patchListCache.get(
- resource.getChange(), psUtil.get(db.get(), resource.getNotes(), old));
+ Change change = resource.getChange();
+ PatchSet patchSet = psUtil.get(db.get(), resource.getNotes(), old);
+ if (patchSet == null) {
+ throw new PatchListNotAvailableException(
+ String.format(
+ "patch set %s of change %s not found",
+ old.get(), change.getId().get()));
+ }
- PatchList curList = patchListCache.get(resource.getChange(), resource.getPatchSet());
+ PatchList oldList = patchListCache.get(change, patchSet);
+
+ PatchList curList = patchListCache.get(change, resource.getPatchSet());
int sz = paths.size();
List<String> pathList = Lists.newArrayListWithCapacity(sz);
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 5ddf9e9..43a487b 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
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.change.DeletePrivate.DeletePrivateByPost;
import com.google.gerrit.server.change.Reviewed.DeleteReviewed;
import com.google.gerrit.server.change.Reviewed.PutReviewed;
@@ -85,7 +86,8 @@
post(CHANGE_KIND, "index").to(Index.class);
post(CHANGE_KIND, "rebuild.notedb").to(Rebuild.class);
post(CHANGE_KIND, "move").to(Move.class);
- put(CHANGE_KIND, "private").to(PutPrivate.class);
+ post(CHANGE_KIND, "private").to(PostPrivate.class);
+ post(CHANGE_KIND, "private.delete").to(DeletePrivateByPost.class);
delete(CHANGE_KIND, "private").to(DeletePrivate.class);
put(CHANGE_KIND, "ignore").to(Ignore.class);
put(CHANGE_KIND, "unignore").to(Unignore.class);
@@ -136,6 +138,8 @@
child(REVISION_KIND, "comments").to(Comments.class);
get(COMMENT_KIND).to(GetComment.class);
+ delete(COMMENT_KIND).to(DeleteComment.class);
+ post(COMMENT_KIND, "delete").to(DeleteComment.class);
child(REVISION_KIND, "robotcomments").to(RobotComments.class);
get(ROBOT_COMMENT_KIND).to(GetRobotComment.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutPrivate.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
similarity index 72%
rename from gerrit-server/src/main/java/com/google/gerrit/server/change/PutPrivate.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
index bd2bf05..a1e673f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutPrivate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
@@ -22,6 +22,8 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
@@ -32,26 +34,30 @@
import com.google.inject.Singleton;
@Singleton
-public class PutPrivate
- extends RetryingRestModifyView<ChangeResource, PutPrivate.Input, Response<String>>
+public class PostPrivate
+ extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, Response<String>>
implements UiAction<ChangeResource> {
- public static class Input {}
-
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
+ private final PermissionBackend permissionBackend;
@Inject
- PutPrivate(Provider<ReviewDb> dbProvider, RetryHelper retryHelper, ChangeMessagesUtil cmUtil) {
+ PostPrivate(
+ Provider<ReviewDb> dbProvider,
+ RetryHelper retryHelper,
+ ChangeMessagesUtil cmUtil,
+ PermissionBackend permissionBackend) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
+ this.permissionBackend = permissionBackend;
}
@Override
- protected Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
+ public Response<String> applyImpl(
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
throws RestApiException, UpdateException {
- if (!rsrc.isUserOwner()) {
+ if (!canSetPrivate(rsrc)) {
throw new AuthException("not allowed to mark private");
}
@@ -60,7 +66,7 @@
}
ChangeControl control = rsrc.getControl();
- SetPrivateOp op = new SetPrivateOp(cmUtil, true);
+ SetPrivateOp op = new SetPrivateOp(cmUtil, true, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(),
@@ -82,6 +88,11 @@
.setVisible(
!change.isPrivate()
&& change.getStatus() != Change.Status.MERGED
- && rsrc.isUserOwner());
+ && canSetPrivate(rsrc));
+ }
+
+ private boolean canSetPrivate(ChangeResource rsrc) {
+ PermissionBackend.WithUser user = permissionBackend.user(rsrc.getUser());
+ return rsrc.isUserOwner() || user.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetPrivateOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetPrivateOp.java
index 1cebcc2..7008eca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetPrivateOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetPrivateOp.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -23,13 +24,25 @@
import com.google.gerrit.server.update.ChangeContext;
import com.google.gwtorm.server.OrmException;
-class SetPrivateOp implements BatchUpdateOp {
+public class SetPrivateOp implements BatchUpdateOp {
+ public static class Input {
+ String message;
+
+ public Input() {}
+
+ public Input(String message) {
+ this.message = message;
+ }
+ }
+
private final ChangeMessagesUtil cmUtil;
private final boolean isPrivate;
+ private final Input input;
- SetPrivateOp(ChangeMessagesUtil cmUtil, boolean isPrivate) {
+ SetPrivateOp(ChangeMessagesUtil cmUtil, boolean isPrivate, Input input) {
this.cmUtil = cmUtil;
this.isPrivate = isPrivate;
+ this.input = input;
}
@Override
@@ -48,10 +61,18 @@
private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
Change c = ctx.getChange();
+ StringBuilder buf = new StringBuilder(c.isPrivate() ? "Set private" : "Unset private");
+
+ String m = Strings.nullToEmpty(input == null ? null : input.message).trim();
+ if (!m.isEmpty()) {
+ buf.append("\n\n");
+ buf.append(m);
+ }
+
ChangeMessage cmsg =
ChangeMessagesUtil.newMessage(
ctx,
- c.isPrivate() ? "Set private" : "Unset private",
+ buf.toString(),
c.isPrivate()
? ChangeMessagesUtil.TAG_SET_PRIVATE
: ChangeMessagesUtil.TAG_UNSET_PRIVATE);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/WorkInProgressOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/WorkInProgressOp.java
index 7f6e543..21e5dfa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -61,7 +61,7 @@
StringBuilder buf =
new StringBuilder(c.isWorkInProgress() ? "Set Work In Progress" : "Set Ready For Review");
- String m = Strings.nullToEmpty(in.message).trim();
+ String m = Strings.nullToEmpty(in == null ? null : in.message).trim();
if (!m.isEmpty()) {
buf.append("\n\n");
buf.append(m);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 8eaa6ec..a8b1837 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -43,6 +43,7 @@
import com.google.gerrit.extensions.events.DraftPublishedListener;
import com.google.gerrit.extensions.events.GarbageCollectorListener;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.GroupIndexedListener;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
import com.google.gerrit.extensions.events.HeadUpdatedListener;
import com.google.gerrit.extensions.events.LifecycleListener;
@@ -328,6 +329,7 @@
DynamicSet.setOf(binder(), PostUploadHook.class);
DynamicSet.setOf(binder(), AccountIndexedListener.class);
DynamicSet.setOf(binder(), ChangeIndexedListener.class);
+ DynamicSet.setOf(binder(), GroupIndexedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
DynamicSet.setOf(binder(), ProjectDeletedListener.class);
DynamicSet.setOf(binder(), GarbageCollectorListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 6bb9eae..3673101 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -57,6 +57,8 @@
public final Path ssh_key;
public final Path ssh_rsa;
public final Path ssh_dsa;
+ public final Path ssh_ecdsa;
+ public final Path ssh_ed25519;
public final Path peer_keys;
public final Path site_css;
@@ -98,6 +100,8 @@
ssh_key = etc_dir.resolve("ssh_host_key");
ssh_rsa = etc_dir.resolve("ssh_host_rsa_key");
ssh_dsa = etc_dir.resolve("ssh_host_dsa_key");
+ ssh_ecdsa = etc_dir.resolve("ssh_host_ecdsa_key");
+ ssh_ed25519 = etc_dir.resolve("ssh_host_ed25519_key");
peer_keys = etc_dir.resolve("peer_keys");
site_css = etc_dir.resolve(CSS_FILENAME);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index d8a19bf..c086f1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -17,18 +17,21 @@
import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
import static com.google.gerrit.server.git.ReceiveCommits.NEW_PATCHSET;
+import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig;
+import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -72,6 +75,7 @@
private final String canonicalWebUrl;
private final DynamicSet<CommitValidationListener> pluginValidators;
private final AllUsersName allUsers;
+ private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
private final String installCommitMsgHookCommand;
@Inject
@@ -80,11 +84,13 @@
@CanonicalWebUrl @Nullable String canonicalWebUrl,
@GerritServerConfig Config cfg,
DynamicSet<CommitValidationListener> pluginValidators,
- AllUsersName allUsers) {
+ AllUsersName allUsers,
+ ExternalIdsConsistencyChecker externalIdsConsistencyChecker) {
this.gerritIdent = gerritIdent;
this.canonicalWebUrl = canonicalWebUrl;
this.pluginValidators = pluginValidators;
this.allUsers = allUsers;
+ this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
this.installCommitMsgHookCommand =
cfg != null ? cfg.getString("gerrit", null, "installCommitMsgHookCommand") : null;
}
@@ -104,7 +110,7 @@
new ConfigValidator(refControl, rw, allUsers),
new BannedCommitsValidator(rejectCommits),
new PluginCommitValidationListener(pluginValidators),
- new BlockExternalIdUpdateListener(allUsers)));
+ new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker)));
}
public CommitValidators forGerritCommits(RefControl refControl, SshInfo sshInfo, RevWalk rw) {
@@ -118,7 +124,7 @@
refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
new ConfigValidator(refControl, rw, allUsers),
new PluginCommitValidationListener(pluginValidators),
- new BlockExternalIdUpdateListener(allUsers)));
+ new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker)));
}
public CommitValidators forMergedCommits(RefControl refControl) {
@@ -582,11 +588,14 @@
}
}
- /** Blocks any update to refs/meta/external-ids */
- public static class BlockExternalIdUpdateListener implements CommitValidationListener {
+ /** Validates updates to refs/meta/external-ids. */
+ public static class ExternalIdUpdateListener implements CommitValidationListener {
private final AllUsersName allUsers;
+ private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
- public BlockExternalIdUpdateListener(AllUsersName allUsers) {
+ public ExternalIdUpdateListener(
+ AllUsersName allUsers, ExternalIdsConsistencyChecker externalIdsConsistencyChecker) {
+ this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
this.allUsers = allUsers;
}
@@ -595,7 +604,26 @@
throws CommitValidationException {
if (allUsers.equals(receiveEvent.project.getNameKey())
&& RefNames.REFS_EXTERNAL_IDS.equals(receiveEvent.refName)) {
- throw new CommitValidationException("not allowed to update " + RefNames.REFS_EXTERNAL_IDS);
+ try {
+ List<ConsistencyProblemInfo> problems =
+ externalIdsConsistencyChecker.check(receiveEvent.commit);
+ List<CommitValidationMessage> msgs =
+ problems
+ .stream()
+ .map(
+ p ->
+ new CommitValidationMessage(
+ p.message, p.status == ConsistencyProblemInfo.Status.ERROR))
+ .collect(toList());
+ if (msgs.stream().anyMatch(m -> m.isError())) {
+ throw new CommitValidationException("invalid external IDs", msgs);
+ }
+ return msgs;
+ } catch (IOException e) {
+ String m = "error validating external IDs";
+ log.warn(m, e);
+ throw new CommitValidationException(m, e);
+ }
}
return Collections.emptyList();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index a9e1362..e1513b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -20,10 +20,12 @@
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.index.IndexRewriter;
import com.google.gerrit.server.index.QueryOptions;
+import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.LimitPredicate;
import com.google.gerrit.server.query.NotPredicate;
@@ -188,6 +190,9 @@
// and included that in their limit computation.
return new LimitPredicate<>(ChangeQueryBuilder.FIELD_LIMIT, opts.limit());
} else if (!isRewritePossible(in)) {
+ if (in instanceof IndexPredicate) {
+ throw new QueryParseException("Unsupported index predicate: " + in.toString());
+ }
return null; // magic to indicate "in" cannot be rewritten
}
@@ -226,7 +231,10 @@
return false;
}
IndexPredicate<ChangeData> p = (IndexPredicate<ChangeData>) in;
- return index.getSchema().hasField(p.getField());
+
+ FieldDef<ChangeData, ?> def = p.getField();
+ Schema<ChangeData> schema = index.getSchema();
+ return schema.hasField(def);
}
private Predicate<ChangeData> partitionChildren(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index f740f58..b137fb3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -16,6 +16,8 @@
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.events.GroupIndexedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.index.Index;
@@ -33,19 +35,28 @@
}
private final GroupCache groupCache;
+ private final DynamicSet<GroupIndexedListener> indexedListener;
private final GroupIndexCollection indexes;
private final GroupIndex index;
@AssistedInject
- GroupIndexerImpl(GroupCache groupCache, @Assisted GroupIndexCollection indexes) {
+ GroupIndexerImpl(
+ GroupCache groupCache,
+ DynamicSet<GroupIndexedListener> indexedListener,
+ @Assisted GroupIndexCollection indexes) {
this.groupCache = groupCache;
+ this.indexedListener = indexedListener;
this.indexes = indexes;
this.index = null;
}
@AssistedInject
- GroupIndexerImpl(GroupCache groupCache, @Assisted GroupIndex index) {
+ GroupIndexerImpl(
+ GroupCache groupCache,
+ DynamicSet<GroupIndexedListener> indexedListener,
+ @Assisted GroupIndex index) {
this.groupCache = groupCache;
+ this.indexedListener = indexedListener;
this.indexes = null;
this.index = index;
}
@@ -55,6 +66,13 @@
for (Index<?, AccountGroup> i : getWriteIndexes()) {
i.replace(groupCache.get(uuid));
}
+ fireGroupIndexedEvent(uuid.get());
+ }
+
+ private void fireGroupIndexedEvent(String uuid) {
+ for (GroupIndexedListener listener : indexedListener) {
+ listener.onGroupIndexed(uuid);
+ }
}
private Collection<GroupIndex> getWriteIndexes() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
index 3362897..f350e63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
@@ -47,7 +47,7 @@
public synchronized void handleEmails(boolean async) {
IMAPClient imap;
if (mailSettings.encryption != Encryption.NONE) {
- imap = new IMAPSClient(mailSettings.encryption.name(), false);
+ imap = new IMAPSClient(mailSettings.encryption.name(), true);
} else {
imap = new IMAPClient();
}
@@ -71,7 +71,8 @@
// Fetch just the internal dates first to know how many messages we
// should fetch.
if (!imap.fetch("1:*", "(INTERNALDATE)")) {
- log.error("IMAP fetch failed. Will retry in next fetch cycle.");
+ // false indicates that there are no messages to fetch
+ log.info("Fetched 0 messages via IMAP");
return;
}
// Format of reply is one line per email and one line to indicate
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 606da44..862da9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -32,12 +32,12 @@
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.change.EmailReviewComments;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.extensions.events.CommentAdded;
@@ -79,11 +79,11 @@
private final PatchListCache patchListCache;
private final PatchSetUtil psUtil;
private final Provider<InternalChangeQuery> queryProvider;
- private final Provider<ReviewDb> reviewDb;
private final DynamicMap<MailFilter> mailFilters;
private final EmailReviewComments.Factory outgoingMailFactory;
private final CommentAdded commentAdded;
private final ApprovalsUtil approvalsUtil;
+ private final AccountCache accountCache;
private final Provider<String> canonicalUrl;
@Inject
@@ -96,11 +96,11 @@
PatchListCache patchListCache,
PatchSetUtil psUtil,
Provider<InternalChangeQuery> queryProvider,
- Provider<ReviewDb> reviewDb,
DynamicMap<MailFilter> mailFilters,
EmailReviewComments.Factory outgoingMailFactory,
ApprovalsUtil approvalsUtil,
CommentAdded commentAdded,
+ AccountCache accountCache,
@CanonicalWebUrl Provider<String> canonicalUrl) {
this.accountByEmailCache = accountByEmailCache;
this.buf = buf;
@@ -110,11 +110,11 @@
this.patchListCache = patchListCache;
this.psUtil = psUtil;
this.queryProvider = queryProvider;
- this.reviewDb = reviewDb;
this.mailFilters = mailFilters;
this.outgoingMailFactory = outgoingMailFactory;
this.commentAdded = commentAdded;
this.approvalsUtil = approvalsUtil;
+ this.accountCache = accountCache;
this.canonicalUrl = canonicalUrl;
}
@@ -153,7 +153,7 @@
return;
}
Account.Id account = accounts.iterator().next();
- if (!reviewDb.get().accounts().get(account).isActive()) {
+ if (!accountCache.get(account).getAccount().isActive()) {
log.warn(String.format("Mail: Account %s is inactive. Will delete message.", account));
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
index e8e2250..d70d651 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
@@ -49,7 +49,7 @@
public synchronized void handleEmails(boolean async) {
POP3Client pop3;
if (mailSettings.encryption != Encryption.NONE) {
- pop3 = new POP3SClient(mailSettings.encryption.name());
+ pop3 = new POP3SClient(mailSettings.encryption.name(), true);
} else {
pop3 = new POP3Client();
}
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 384daa8..fcde617 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
@@ -124,9 +124,10 @@
}
private final AccountCache accountCache;
+ private final NoteDbUpdateManager.Factory updateManagerFactory;
private final ChangeDraftUpdate.Factory draftUpdateFactory;
private final RobotCommentUpdate.Factory robotCommentUpdateFactory;
- private final NoteDbUpdateManager.Factory updateManagerFactory;
+ private final DeleteCommentRewriter.Factory deleteCommentRewriterFactory;
private final Table<String, Account.Id, Optional<Short>> approvals;
private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>();
@@ -158,6 +159,7 @@
private ChangeDraftUpdate draftUpdate;
private RobotCommentUpdate robotCommentUpdate;
+ private DeleteCommentRewriter deleteCommentRewriter;
@AssistedInject
private ChangeUpdate(
@@ -169,6 +171,7 @@
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
RobotCommentUpdate.Factory robotCommentUpdateFactory,
+ DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
ChangeNoteUtil noteUtil) {
@@ -181,6 +184,7 @@
updateManagerFactory,
draftUpdateFactory,
robotCommentUpdateFactory,
+ deleteCommentRewriterFactory,
projectCache,
ctl,
serverIdent.getWhen(),
@@ -197,6 +201,7 @@
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
RobotCommentUpdate.Factory robotCommentUpdateFactory,
+ DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
@Assisted Date when,
@@ -210,6 +215,7 @@
updateManagerFactory,
draftUpdateFactory,
robotCommentUpdateFactory,
+ deleteCommentRewriterFactory,
ctl,
when,
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
@@ -235,15 +241,17 @@
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
RobotCommentUpdate.Factory robotCommentUpdateFactory,
+ DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
@Assisted ChangeControl ctl,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator,
ChangeNoteUtil noteUtil) {
super(cfg, migration, ctl, serverIdent, anonymousCowardName, noteUtil, when);
this.accountCache = accountCache;
+ this.updateManagerFactory = updateManagerFactory;
this.draftUpdateFactory = draftUpdateFactory;
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
- this.updateManagerFactory = updateManagerFactory;
+ this.deleteCommentRewriterFactory = deleteCommentRewriterFactory;
this.approvals = approvals(labelNameComparator);
}
@@ -257,6 +265,7 @@
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
RobotCommentUpdate.Factory robotCommentUpdateFactory,
+ DeleteCommentRewriter.Factory deleteCommentRewriterFactory,
ChangeNoteUtil noteUtil,
@Assisted Change change,
@Assisted("effective") @Nullable Account.Id accountId,
@@ -280,6 +289,7 @@
this.draftUpdateFactory = draftUpdateFactory;
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
this.updateManagerFactory = updateManagerFactory;
+ this.deleteCommentRewriterFactory = deleteCommentRewriterFactory;
this.approvals = approvals(labelNameComparator);
}
@@ -394,6 +404,11 @@
createDraftUpdateIfNull().deleteComment(c);
}
+ public void deleteCommentByRewritingHistory(String uuid, String newMessage) {
+ deleteCommentRewriter =
+ deleteCommentRewriterFactory.create(getChange().getId(), uuid, newMessage);
+ }
+
@VisibleForTesting
ChangeDraftUpdate createDraftUpdateIfNull() {
if (draftUpdate == null) {
@@ -596,6 +611,8 @@
@Override
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins, ObjectId curr)
throws OrmException, IOException {
+ checkState(deleteCommentRewriter == null, "cannot update and rewrite ref in one BatchUpdate");
+
CommitBuilder cb = new CommitBuilder();
int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
@@ -798,6 +815,10 @@
return robotCommentUpdate;
}
+ public DeleteCommentRewriter getDeleteCommentRewriter() {
+ return deleteCommentRewriter;
+ }
+
public void setAllowWriteToNewRef(boolean allow) {
isAllowWriteToNewtRef = allow;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
new file mode 100644
index 0000000..8a43bc6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
@@ -0,0 +1,246 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Deletes a published comment from NoteDb by rewriting the commit history. Instead of deleting the
+ * whole comment, it just replaces the comment's message with a new message.
+ */
+public class DeleteCommentRewriter implements NoteDbRewriter {
+
+ public interface Factory {
+ /**
+ * Creates a DeleteCommentRewriter instance.
+ *
+ * @param id the id of the change which contains the target comment.
+ * @param uuid the uuid of the target comment.
+ * @param newMessage the message used to replace the old message of the target comment.
+ * @return the DeleteCommentRewriter instance
+ */
+ DeleteCommentRewriter create(
+ Change.Id id, @Assisted("uuid") String uuid, @Assisted("newMessage") String newMessage);
+ }
+
+ private final ChangeNoteUtil noteUtil;
+ private final Change.Id changeId;
+ private final String uuid;
+ private final String newMessage;
+
+ @Inject
+ DeleteCommentRewriter(
+ ChangeNoteUtil noteUtil,
+ @Assisted Change.Id changeId,
+ @Assisted("uuid") String uuid,
+ @Assisted("newMessage") String newMessage) {
+ this.noteUtil = noteUtil;
+ this.changeId = changeId;
+ this.uuid = uuid;
+ this.newMessage = newMessage;
+ }
+
+ @Override
+ public String getRefName() {
+ return RefNames.changeMetaRef(changeId);
+ }
+
+ @Override
+ public ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip)
+ throws IOException, ConfigInvalidException, OrmException {
+ checkArgument(!currTip.equals(ObjectId.zeroId()));
+
+ // Walk from the first commit of the branch.
+ revWalk.reset();
+ revWalk.markStart(revWalk.parseCommit(currTip));
+ revWalk.sort(RevSort.REVERSE);
+
+ ObjectReader reader = revWalk.getObjectReader();
+ ObjectId newTip = revWalk.next(); // The first commit will not be rewritten.
+ NoteMap newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
+ Map<String, Comment> parentComments =
+ getPublishedComments(noteUtil, changeId, reader, newTipNoteMap);
+
+ boolean rewrite = false;
+ RevCommit originalCommit;
+ while ((originalCommit = revWalk.next()) != null) {
+ NoteMap noteMap = NoteMap.read(reader, originalCommit);
+ Map<String, Comment> currComments = getPublishedComments(noteUtil, changeId, reader, noteMap);
+
+ if (!rewrite && currComments.containsKey(uuid)) {
+ rewrite = true;
+ }
+
+ if (!rewrite) {
+ parentComments = currComments;
+ newTip = originalCommit;
+ continue;
+ }
+
+ List<Comment> putInComments = getPutInComments(parentComments, currComments);
+ List<Comment> deletedComments = getDeletedComments(parentComments, currComments);
+ newTip =
+ rewriteCommit(
+ originalCommit,
+ newTipNoteMap,
+ newTip,
+ inserter,
+ reader,
+ putInComments,
+ deletedComments);
+ newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
+ parentComments = currComments;
+ }
+
+ return newTip;
+ }
+
+ /**
+ * Gets all the comments which are presented at a commit. Note they include the comments put in by
+ * the previous commits.
+ */
+ @VisibleForTesting
+ public static Map<String, Comment> getPublishedComments(
+ ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap)
+ throws IOException, ConfigInvalidException {
+ return RevisionNoteMap.parse(noteUtil, changeId, reader, noteMap, PUBLISHED)
+ .revisionNotes
+ .values()
+ .stream()
+ .flatMap(n -> n.getComments().stream())
+ .collect(Collectors.toMap(c -> c.key.uuid, c -> c));
+ }
+
+ /**
+ * Gets the comments put in by the current commit. The message of the target comment will be
+ * replaced by the new message.
+ *
+ * @param parMap the comment map of the parent commit.
+ * @param curMap the comment map of the current commit.
+ * @return The comments put in by the current commit.
+ */
+ private List<Comment> getPutInComments(Map<String, Comment> parMap, Map<String, Comment> curMap) {
+ List<Comment> comments = new ArrayList<>();
+ for (String key : curMap.keySet()) {
+ if (!parMap.containsKey(key)) {
+ Comment comment = curMap.get(key);
+ if (key.equals(uuid)) {
+ comment.message = newMessage;
+ }
+ comments.add(comment);
+ }
+ }
+ return comments;
+ }
+
+ /**
+ * Gets the comments deleted by the current commit.
+ *
+ * @param parMap the comment map of the parent commit.
+ * @param curMap the comment map of the current commit.
+ * @return The comments deleted by the current commit.
+ */
+ private List<Comment> getDeletedComments(
+ Map<String, Comment> parMap, Map<String, Comment> curMap) {
+ return parMap
+ .entrySet()
+ .stream()
+ .filter(c -> !curMap.containsKey(c.getKey()))
+ .map(c -> c.getValue())
+ .collect(toList());
+ }
+
+ /**
+ * Rewrites one commit.
+ *
+ * @param originalCommit the original commit to be rewritten.
+ * @param parentNoteMap the {@code NoteMap} of the new commit's parent.
+ * @param parentId the {@code ObjectId} of the new commit's parent.
+ * @param inserter the {@code ObjectInserter} for the rewrite process.
+ * @param reader the {@code ObjectReader} for the rewrite process.
+ * @param putInComments the comments put in by this commit.
+ * @param deletedComments the comments deleted by this commit.
+ * @return the {@code objectId} of the new commit.
+ * @throws IOException
+ * @throws ConfigInvalidException
+ */
+ private ObjectId rewriteCommit(
+ RevCommit originalCommit,
+ NoteMap parentNoteMap,
+ ObjectId parentId,
+ ObjectInserter inserter,
+ ObjectReader reader,
+ List<Comment> putInComments,
+ List<Comment> deletedComments)
+ throws IOException, ConfigInvalidException {
+ RevisionNoteMap<ChangeRevisionNote> revNotesMap =
+ RevisionNoteMap.parse(noteUtil, changeId, reader, parentNoteMap, PUBLISHED);
+ RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
+
+ for (Comment c : putInComments) {
+ cache.get(new RevId(c.revId)).putComment(c);
+ }
+
+ for (Comment c : deletedComments) {
+ cache.get(new RevId(c.revId)).deleteComment(c.key);
+ }
+
+ Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
+ for (Map.Entry<RevId, RevisionNoteBuilder> entry : builders.entrySet()) {
+ ObjectId objectId = ObjectId.fromString(entry.getKey().get());
+ byte[] data = entry.getValue().build(noteUtil, noteUtil.getWriteJson());
+ if (data.length == 0) {
+ revNotesMap.noteMap.remove(objectId);
+ }
+ revNotesMap.noteMap.set(objectId, inserter.insert(OBJ_BLOB, data));
+ }
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setParentId(parentId);
+ cb.setTreeId(revNotesMap.noteMap.writeTree(inserter));
+ cb.setMessage(originalCommit.getFullMessage());
+ cb.setCommitter(originalCommit.getCommitterIdent());
+ cb.setAuthor(originalCommit.getAuthorIdent());
+ cb.setEncoding(originalCommit.getEncoding());
+
+ return inserter.insert(cb);
+ }
+}
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 d249689..64b8b44 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
@@ -49,6 +49,7 @@
public void configure() {
factory(ChangeUpdate.Factory.class);
factory(ChangeDraftUpdate.Factory.class);
+ factory(DeleteCommentRewriter.Factory.class);
factory(DraftCommentNotes.Factory.class);
factory(RobotCommentUpdate.Factory.class);
factory(RobotCommentNotes.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbRewriter.java
new file mode 100644
index 0000000..3c7b0a3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbRewriter.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public interface NoteDbRewriter {
+
+ /** Gets the name of the target ref which will be rewritten. */
+ String getRefName();
+
+ /**
+ * Rewrites the commit history.
+ *
+ * @param revWalk a {@code RevWalk} instance.
+ * @param inserter a {@code ObjectInserter} instance.
+ * @param currTip the {@code ObjectId} of the ref's tip commit.
+ * @return the {@code ObjectId} of the ref's new tip commit.
+ */
+ ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip)
+ throws IOException, ConfigInvalidException, OrmException;
+}
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 3aa2748..6b3492a 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
@@ -54,6 +54,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -205,6 +206,7 @@
private final ListMultimap<String, ChangeUpdate> changeUpdates;
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
private final ListMultimap<String, RobotCommentUpdate> robotCommentUpdates;
+ private final ListMultimap<String, NoteDbRewriter> rewriters;
private final Set<Change.Id> toDelete;
private OpenRepo changeRepo;
@@ -232,6 +234,7 @@
changeUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
robotCommentUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
+ rewriters = MultimapBuilder.hashKeys().arrayListValues().build();
toDelete = new HashSet<>();
}
@@ -344,6 +347,7 @@
return changeUpdates.isEmpty()
&& draftUpdates.isEmpty()
&& robotCommentUpdates.isEmpty()
+ && rewriters.isEmpty()
&& toDelete.isEmpty()
&& !hasCommands(changeRepo)
&& !hasCommands(allUsersRepo);
@@ -377,6 +381,10 @@
if (rcu != null) {
robotCommentUpdates.put(rcu.getRefName(), rcu);
}
+ DeleteCommentRewriter deleteCommentRewriter = update.getDeleteCommentRewriter();
+ if (deleteCommentRewriter != null) {
+ rewriters.put(deleteCommentRewriter.getRefName(), deleteCommentRewriter);
+ }
}
public void add(ChangeDraftUpdate draftUpdate) {
@@ -603,6 +611,21 @@
if (!robotCommentUpdates.isEmpty()) {
addUpdates(robotCommentUpdates, changeRepo);
}
+ if (!rewriters.isEmpty()) {
+ Optional<String> conflictKey =
+ rewriters
+ .keySet()
+ .stream()
+ .filter(k -> (draftUpdates.containsKey(k) || robotCommentUpdates.containsKey(k)))
+ .findAny();
+ if (conflictKey.isPresent()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "cannot update and rewrite ref %s in one BatchUpdate", conflictKey.get()));
+ }
+ addRewrites(rewriters, changeRepo);
+ }
+
for (Change.Id id : toDelete) {
doDelete(id);
}
@@ -723,6 +746,35 @@
}
}
+ private static void addRewrites(ListMultimap<String, NoteDbRewriter> rewriters, OpenRepo openRepo)
+ throws OrmException, IOException {
+ for (Map.Entry<String, Collection<NoteDbRewriter>> entry : rewriters.asMap().entrySet()) {
+ String refName = entry.getKey();
+ ObjectId oldTip = openRepo.cmds.get(refName).orElse(ObjectId.zeroId());
+
+ if (oldTip.equals(ObjectId.zeroId())) {
+ throw new OrmException(String.format("Ref %s is empty", refName));
+ }
+
+ ObjectId currTip = oldTip;
+ try {
+ for (NoteDbRewriter noteDbRewriter : entry.getValue()) {
+ ObjectId nextTip =
+ noteDbRewriter.rewriteCommitHistory(openRepo.rw, openRepo.tempIns, currTip);
+ if (nextTip != null) {
+ currTip = nextTip;
+ }
+ }
+ } catch (ConfigInvalidException e) {
+ throw new OrmException("Cannot rewrite commit history", e);
+ }
+
+ if (!oldTip.equals(currTip)) {
+ openRepo.cmds.add(new ReceiveCommand(oldTip, currTip, refName));
+ }
+ }
+ }
+
private static <U extends AbstractChangeUpdate> boolean allowWrite(
Collection<U> updates, ObjectId old) {
if (!old.equals(ObjectId.zeroId())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 8fabe44..16c63c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -286,6 +286,7 @@
/** Can this user rebase this change? */
private boolean canRebase(ReviewDb db) throws OrmException {
return (isOwner() || getRefControl().canSubmit(isOwner()) || getRefControl().canRebase())
+ && getRefControl().canUpload()
&& !isPatchSetLocked(db);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index c8857eb..8891af5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -124,7 +124,7 @@
@Override
protected Predicate<AccountState> defaultField(String query) {
Predicate<AccountState> defaultPredicate = AccountPredicates.defaultPredicate(query);
- if ("self".equalsIgnoreCase(query)) {
+ if ("self".equalsIgnoreCase(query) || "me".equalsIgnoreCase(query)) {
try {
return Predicate.or(defaultPredicate, AccountPredicates.id(self()));
} catch (QueryParseException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 099a3d1..611a1a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -118,6 +118,8 @@
private static final Pattern DEF_CHANGE =
Pattern.compile("^(?:[1-9][0-9]*|(?:[^~]+~[^~]+~)?[iI][0-9a-f]{4,}.*)$");
+ private static final int MAX_ACCOUNTS_PER_DEFAULT_FIELD = 10;
+
// NOTE: As new search operations are added, please keep the
// SearchSuggestOracle up to date.
@@ -884,9 +886,13 @@
return new HasDraftByPredicate(who);
}
+ private boolean isSelf(String who) {
+ return "self".equals(who) || "me".equals(who);
+ }
+
@Operator
public Predicate<ChangeData> visibleto(String who) throws QueryParseException, OrmException {
- if ("self".equals(who)) {
+ if (isSelf(who)) {
return is_visible();
}
Set<Account.Id> m = args.accountResolver.findAll(args.db.get(), who);
@@ -939,6 +945,15 @@
return Predicate.or(p);
}
+ private Predicate<ChangeData> ownerDefaultField(String who)
+ throws QueryParseException, OrmException {
+ Set<Account.Id> accounts = parseAccount(who);
+ if (accounts.size() > MAX_ACCOUNTS_PER_DEFAULT_FIELD) {
+ return Predicate.any();
+ }
+ return owner(accounts);
+ }
+
@Operator
public Predicate<ChangeData> assignee(String who) throws QueryParseException, OrmException {
return assignee(parseAccount(who));
@@ -968,17 +983,27 @@
@Operator
public Predicate<ChangeData> reviewer(String who) throws QueryParseException, OrmException {
+ return reviewer(who, false);
+ }
+
+ private Predicate<ChangeData> reviewerDefaultField(String who)
+ throws QueryParseException, OrmException {
+ return reviewer(who, true);
+ }
+
+ private Predicate<ChangeData> reviewer(String who, boolean forDefaultField)
+ throws QueryParseException, OrmException {
if (args.getSchema().hasField(ChangeField.WIP)) {
return Predicate.and(
Predicate.not(new BooleanPredicate(ChangeField.WIP, args.fillArgs)),
- reviewerByState(who, ReviewerStateInternal.REVIEWER));
+ reviewerByState(who, ReviewerStateInternal.REVIEWER, forDefaultField));
}
- return reviewerByState(who, ReviewerStateInternal.REVIEWER);
+ return reviewerByState(who, ReviewerStateInternal.REVIEWER, forDefaultField);
}
@Operator
public Predicate<ChangeData> cc(String who) throws QueryParseException, OrmException {
- return reviewerByState(who, ReviewerStateInternal.CC);
+ return reviewerByState(who, ReviewerStateInternal.CC, false);
}
@Operator
@@ -1137,12 +1162,12 @@
// Adapt the capacity of this list when adding more default predicates.
List<Predicate<ChangeData>> predicates = Lists.newArrayListWithCapacity(11);
try {
- predicates.add(owner(query));
+ predicates.add(ownerDefaultField(query));
} catch (OrmException | QueryParseException e) {
// Skip.
}
try {
- predicates.add(reviewer(query));
+ predicates.add(reviewerDefaultField(query));
} catch (OrmException | QueryParseException e) {
// Skip.
}
@@ -1165,7 +1190,7 @@
}
private Set<Account.Id> parseAccount(String who) throws QueryParseException, OrmException {
- if ("self".equals(who)) {
+ if (isSelf(who)) {
return Collections.singleton(self());
}
Set<Account.Id> matches = args.accountResolver.findAll(args.db.get(), who);
@@ -1208,7 +1233,8 @@
return args.getIdentifiedUser().getAccountId();
}
- public Predicate<ChangeData> reviewerByState(String who, ReviewerStateInternal state)
+ public Predicate<ChangeData> reviewerByState(
+ String who, ReviewerStateInternal state, boolean forDefaultField)
throws QueryParseException, OrmException {
Predicate<ChangeData> reviewerByEmailPredicate = null;
if (args.index.getSchema().hasField(ChangeField.REVIEWER_BY_EMAIL)) {
@@ -1220,12 +1246,15 @@
Predicate<ChangeData> reviewerPredicate = null;
try {
- reviewerPredicate =
- Predicate.or(
- parseAccount(who)
- .stream()
- .map(id -> ReviewerPredicate.forState(args, id, state))
- .collect(toList()));
+ Set<Account.Id> accounts = parseAccount(who);
+ if (!forDefaultField || accounts.size() <= MAX_ACCOUNTS_PER_DEFAULT_FIELD) {
+ reviewerPredicate =
+ Predicate.or(
+ accounts
+ .stream()
+ .map(id -> ReviewerPredicate.forState(args, id, state))
+ .collect(toList()));
+ }
} catch (QueryParseException e) {
// Propagate this exception only if we can't use 'who' to query by email
if (reviewerByEmailPredicate == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index be41a0a..76decec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -35,7 +35,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_149> C = Schema_149.class;
+ public static final Class<Schema_150> C = Schema_150.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java
index df808df..e67ae2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java
@@ -14,16 +14,24 @@
package com.google.gerrit.server.schema;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+
+import com.google.common.base.Strings;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.HashedPassword;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.List;
+import java.sql.Statement;
public class Schema_142 extends SchemaVersion {
+ private static final int MAX_BATCH_SIZE = 1000;
+
@Inject
Schema_142(Provider<Schema_141> prior) {
super(prior);
@@ -31,19 +39,39 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- List<AccountExternalId> newIds = db.accountExternalIds().all().toList();
- for (AccountExternalId id : newIds) {
- if (!id.isScheme(AccountExternalId.SCHEME_USERNAME)) {
- continue;
+ try (PreparedStatement updateStmt =
+ ((JdbcSchema) db)
+ .getConnection()
+ .prepareStatement(
+ "UPDATE account_external_ids " + "SET password = ? " + "WHERE external_id = ?")) {
+ int batchCount = 0;
+
+ try (Statement stmt = newStatement(db);
+ ResultSet rs =
+ stmt.executeQuery("SELECT external_id, password FROM account_external_ids")) {
+ while (rs.next()) {
+ String externalId = rs.getString("external_id");
+ String password = rs.getString("password");
+ if (!ExternalId.Key.parse(externalId).isScheme(SCHEME_USERNAME)
+ || Strings.isNullOrEmpty(password)) {
+ continue;
+ }
+
+ HashedPassword hashed = HashedPassword.fromPassword(password);
+ updateStmt.setString(1, hashed.encode());
+ updateStmt.setString(2, externalId);
+ updateStmt.addBatch();
+ batchCount++;
+ if (batchCount >= MAX_BATCH_SIZE) {
+ updateStmt.executeBatch();
+ batchCount = 0;
+ }
+ }
}
- String password = id.getPassword();
- if (password != null) {
- HashedPassword hashed = HashedPassword.fromPassword(password);
- id.setPassword(hashed.encode());
+ if (batchCount > 0) {
+ updateStmt.executeBatch();
}
}
-
- db.accountExternalIds().upsert(newIds);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
index 70e55cf..eaa97e4d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.schema;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -21,11 +22,15 @@
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
+import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
@@ -56,7 +61,26 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- Set<ExternalId> toAdd = ExternalId.from(db.accountExternalIds().all().toList());
+ Set<ExternalId> toAdd = new HashSet<>();
+ try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ ResultSet rs =
+ stmt.executeQuery(
+ "SELECT "
+ + "account_id, "
+ + "email_address, "
+ + "password, "
+ + "external_id "
+ + "FROM account_external_ids")) {
+ while (rs.next()) {
+ Account.Id accountId = new Account.Id(rs.getInt(1));
+ String email = rs.getString(2);
+ String password = rs.getString(3);
+ String externalId = rs.getString(4);
+
+ toAdd.add(ExternalId.create(ExternalId.Key.parse(externalId), accountId, email, password));
+ }
+ }
+
try {
try (Repository repo = repoManager.openRepository(allUsersName);
RevWalk rw = new RevWalk(repo);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_150.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_150.java
new file mode 100644
index 0000000..4830fe1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_150.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/** Drop ACCOUNT_EXTERNAL_IDS table. */
+public class Schema_150 extends SchemaVersion {
+ @Inject
+ Schema_150(Provider<Schema_149> prior) {
+ super(prior);
+ }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
index cb1d97b..b80e31e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
@@ -29,7 +29,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.AndChangeSource;
@@ -196,9 +195,10 @@
assertThat(rewrite(in)).isEqualTo(query(in));
indexes.setSearchIndex(new FakeChangeIndex(FakeChangeIndex.V1));
- Predicate<ChangeData> out = rewrite(in);
- assertThat(out).isInstanceOf(AndPredicate.class);
- assertThat(out.getChildren()).containsExactly(query(in.getChild(0)), in.getChild(1)).inOrder();
+
+ exception.expect(QueryParseException.class);
+ exception.expectMessage("Unsupported index predicate: file:a");
+ rewrite(in);
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 3d13536..ee894c9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -98,6 +98,7 @@
@Inject protected AllProjectsName allProjects;
protected LifecycleManager lifecycle;
+ protected Injector injector;
protected ReviewDb db;
protected AccountInfo currentUserInfo;
protected CurrentUser user;
@@ -107,11 +108,14 @@
@Before
public void setUpInjector() throws Exception {
lifecycle = new LifecycleManager();
- Injector injector = createInjector();
+ injector = createInjector();
lifecycle.add(injector);
injector.injectMembers(this);
lifecycle.start();
+ setUpDatabase();
+ }
+ protected void setUpDatabase() throws Exception {
db = schemaFactory.open();
schemaCreator.create(db);
@@ -255,6 +259,7 @@
assertQuery("Jo Do", user1);
assertQuery("jo do", user1);
assertQuery("self", currentUserInfo, user3);
+ assertQuery("me", currentUserInfo);
assertQuery("name:John", user1);
assertQuery("name:john", user1);
assertQuery("name:Doe", user1);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 34ef0b5..0b01362 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -399,7 +399,7 @@
assertQuery("is:open", change2, change1);
assertQuery("is:private");
- gApi.changes().id(change1.getChangeId()).setPrivate(true);
+ gApi.changes().id(change1.getChangeId()).setPrivate(true, null);
// Change1 is not private, but should be still visible to its owner.
assertQuery("is:open", change1, change2);
@@ -1988,6 +1988,19 @@
.containsExactlyElementsIn(expectedPatterns);
}
+ @Test
+ public void selfAndMe() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo));
+ Change change2 = insert(repo, newChange(repo), userId);
+ insert(repo, newChange(repo));
+ gApi.accounts().self().starChange(change1.getId().toString());
+ gApi.accounts().self().starChange(change2.getId().toString());
+
+ assertQuery("starredby:self", change2, change1);
+ assertQuery("starredby:me", change2, change1);
+ }
+
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
return newChange(repo, null, null, null, null);
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index a0e5ee0..4b8309a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -98,6 +98,7 @@
@Inject protected GroupCache groupCache;
protected LifecycleManager lifecycle;
+ protected Injector injector;
protected ReviewDb db;
protected AccountInfo currentUserInfo;
protected CurrentUser user;
@@ -107,11 +108,14 @@
@Before
public void setUpInjector() throws Exception {
lifecycle = new LifecycleManager();
- Injector injector = createInjector();
+ injector = createInjector();
lifecycle.add(injector);
injector.injectMembers(this);
lifecycle.start();
+ setUpDatabase();
+ }
+ protected void setUpDatabase() throws Exception {
db = schemaFactory.open();
schemaCreator.create(db);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
index 21c8764..3d4a1a0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
@@ -54,10 +54,11 @@
import com.google.common.io.ByteStreams;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URL;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -119,7 +120,7 @@
try (InputStream in = url.openStream()) {
hook = File.createTempFile("hook_", ".sh");
cleanup.add(hook);
- try (FileOutputStream out = new FileOutputStream(hook)) {
+ try (OutputStream out = Files.newOutputStream(hook.toPath())) {
ByteStreams.copy(in, out);
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
index 885a1f5..870dba7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/DisabledReviewDb.java
@@ -15,7 +15,6 @@
package com.google.gerrit.testutil;
import com.google.gerrit.reviewdb.server.AccountAccess;
-import com.google.gerrit.reviewdb.server.AccountExternalIdAccess;
import com.google.gerrit.reviewdb.server.AccountGroupAccess;
import com.google.gerrit.reviewdb.server.AccountGroupByIdAccess;
import com.google.gerrit.reviewdb.server.AccountGroupByIdAudAccess;
@@ -89,11 +88,6 @@
}
@Override
- public AccountExternalIdAccess accountExternalIds() {
- throw new Disabled();
- }
-
- @Override
public AccountGroupAccess accountGroups() {
throw new Disabled();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 5f425d1..d655500 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -81,7 +81,11 @@
}
private static Set<PublicKey> myHostKeys(KeyPairProvider p) {
- final Set<PublicKey> keys = new HashSet<>(2);
+ final Set<PublicKey> keys = new HashSet<>(6);
+ addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
return keys;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
index 589014c..8764357 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
@@ -25,7 +25,6 @@
import java.util.List;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
class HostKeyProvider implements Provider<KeyPairProvider> {
@@ -41,14 +40,22 @@
Path objKey = site.ssh_key;
Path rsaKey = site.ssh_rsa;
Path dsaKey = site.ssh_dsa;
+ Path ecdsaKey = site.ssh_ecdsa;
+ Path ed25519Key = site.ssh_ed25519;
- final List<File> stdKeys = new ArrayList<>(2);
+ final List<File> stdKeys = new ArrayList<>(4);
if (Files.exists(rsaKey)) {
stdKeys.add(rsaKey.toAbsolutePath().toFile());
}
if (Files.exists(dsaKey)) {
stdKeys.add(dsaKey.toAbsolutePath().toFile());
}
+ if (Files.exists(ecdsaKey)) {
+ stdKeys.add(ecdsaKey.toAbsolutePath().toFile());
+ }
+ if (Files.exists(ed25519Key)) {
+ stdKeys.add(ed25519Key.toAbsolutePath().toFile());
+ }
if (Files.exists(objKey)) {
if (stdKeys.isEmpty()) {
@@ -65,13 +72,6 @@
if (stdKeys.isEmpty()) {
throw new ProvisionException("No SSH keys under " + site.etc_dir);
}
- if (!SecurityUtils.isBouncyCastleRegistered()) {
- throw new ProvisionException(
- "Bouncy Castle Crypto not installed;"
- + " needed to read server host keys: "
- + stdKeys
- + "");
- }
FileKeyPairProvider kp = new FileKeyPairProvider();
kp.setFiles(stdKeys);
return kp;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 45f68e8..3c108b0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -84,7 +84,6 @@
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.mac.Mac;
-import org.apache.sshd.common.random.JceRandomFactory;
import org.apache.sshd.common.random.Random;
import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.session.ConnectionService;
@@ -189,6 +188,7 @@
long idleTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null, "idleTimeout", 0, SECONDS);
getProperties().put(IDLE_TIMEOUT, String.valueOf(SECONDS.toMillis(idleTimeoutSeconds)));
+ getProperties().put(NIO2_READ_TIMEOUT, String.valueOf(SECONDS.toMillis(idleTimeoutSeconds)));
long rekeyTimeLimit =
ConfigUtil.getTimeUnit(cfg, "sshd", null, "rekeyTimeLimit", 3600, SECONDS);
@@ -217,11 +217,7 @@
? MinaServiceFactoryFactory.class.getName()
: Nio2ServiceFactoryFactory.class.getName());
- if (SecurityUtils.isBouncyCastleRegistered()) {
- initProviderBouncyCastle(cfg);
- } else {
- initProviderJce();
- }
+ initProviderBouncyCastle(cfg);
initCiphers(cfg);
initKeyExchanges(cfg);
initMacs(cfg);
@@ -405,7 +401,9 @@
try {
r.add(new HostKey(addr, keyBin));
} catch (JSchException e) {
- sshDaemonLog.warn("Cannot format SSHD host key", e);
+ sshDaemonLog.warn(
+ String.format(
+ "Cannot format SSHD host key [%s]: %s", pub.getAlgorithm(), e.getMessage()));
}
}
}
@@ -414,7 +412,11 @@
private List<PublicKey> myHostKeys() {
final KeyPairProvider p = getKeyPairProvider();
- final List<PublicKey> keys = new ArrayList<>(2);
+ final List<PublicKey> keys = new ArrayList<>(6);
+ addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
return keys;
@@ -520,10 +522,6 @@
}
}
- private void initProviderJce() {
- setRandomFactory(new SingletonRandomFactory(JceRandomFactory.INSTANCE));
- }
-
@SuppressWarnings("unchecked")
private void initCiphers(final Config cfg) {
final List<NamedFactory<Cipher>> a = BaseBuilder.setUpDefaultCiphers(true);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index a3cf7c1..6a68211 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -19,14 +19,12 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gerrit.reviewdb.client.AccountSshKey;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gerrit.server.ssh.SshKeyCreator;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
@@ -92,40 +90,33 @@
}
static class Loader extends CacheLoader<String, Iterable<SshKeyCacheEntry>> {
- private final SchemaFactory<ReviewDb> schema;
private final ExternalIds externalIds;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
@Inject
- Loader(
- SchemaFactory<ReviewDb> schema,
- ExternalIds externalIds,
- VersionedAuthorizedKeys.Accessor authorizedKeys) {
- this.schema = schema;
+ Loader(ExternalIds externalIds, VersionedAuthorizedKeys.Accessor authorizedKeys) {
this.externalIds = externalIds;
this.authorizedKeys = authorizedKeys;
}
@Override
public Iterable<SshKeyCacheEntry> load(String username) throws Exception {
- try (ReviewDb db = schema.open()) {
- ExternalId user = externalIds.get(db, ExternalId.Key.create(SCHEME_USERNAME, username));
- if (user == null) {
- return NO_SUCH_USER;
- }
-
- List<SshKeyCacheEntry> kl = new ArrayList<>(4);
- for (AccountSshKey k : authorizedKeys.getKeys(user.accountId())) {
- if (k.isValid()) {
- add(kl, k);
- }
- }
-
- if (kl.isEmpty()) {
- return NO_KEYS;
- }
- return Collections.unmodifiableList(kl);
+ ExternalId user = externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, username));
+ if (user == null) {
+ return NO_SUCH_USER;
}
+
+ List<SshKeyCacheEntry> kl = new ArrayList<>(4);
+ for (AccountSshKey k : authorizedKeys.getKeys(user.accountId())) {
+ if (k.isValid()) {
+ add(kl, k);
+ }
+ }
+
+ if (kl.isEmpty()) {
+ return NO_KEYS;
+ }
+ return Collections.unmodifiableList(kl);
}
private void add(List<SshKeyCacheEntry> kl, AccountSshKey k) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 5a47cb0..820052c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -25,12 +25,11 @@
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
+import java.nio.file.Files;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -54,6 +53,7 @@
@Inject private PluginLoader loader;
+ @SuppressWarnings("resource")
@Override
protected void run() throws UnloggedFailure {
if (!loader.isRemoteAdminEnabled()) {
@@ -80,8 +80,8 @@
data = in;
} else if (new File(source).isFile() && source.equals(new File(source).getAbsolutePath())) {
try {
- data = new FileInputStream(new File(source));
- } catch (FileNotFoundException e) {
+ data = Files.newInputStream(new File(source).toPath());
+ } catch (IOException e) {
throw die("cannot read " + source);
}
} else {
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
index b5a1dae..ec92fba 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
@@ -20,10 +20,10 @@
import com.google.gerrit.pgm.init.PluginsDistribution;
import com.google.inject.Singleton;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
@@ -45,7 +45,7 @@
for (File p : list) {
String pluginJarName = p.getName();
String pluginName = pluginJarName.substring(0, pluginJarName.length() - JAR.length());
- try (InputStream in = new FileInputStream(p)) {
+ try (InputStream in = Files.newInputStream(p.toPath())) {
processor.process(pluginName, in);
}
}
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index d765cc1..219cc24 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -15,12 +15,12 @@
import com.google.common.io.ByteStreams;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -162,7 +162,7 @@
if (bazel) {
renderFiles(inputFiles, null);
} else {
- try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(zipFile))) {
+ try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(Paths.get(zipFile)))) {
renderFiles(inputFiles, zip);
File[] cssFiles =
@@ -199,7 +199,7 @@
public static void zipFile(File file, String name, ZipOutputStream zip) throws IOException {
zip.putNextEntry(new ZipEntry(name));
- try (FileInputStream input = new FileInputStream(file)) {
+ try (InputStream input = Files.newInputStream(file.toPath())) {
ByteStreams.copy(input, zip);
}
zip.closeEntry();
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 395f9fe..fbb7f94 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -18,13 +18,13 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
@@ -81,7 +81,7 @@
return;
}
- try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(outFile))) {
+ try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(Paths.get(outFile)))) {
byte[] compressedIndex = zip(index());
JarEntry entry = new JarEntry(String.format("%s/%s", Constants.PACKAGE, Constants.INDEX_ZIP));
entry.setSize(compressedIndex.length);
@@ -106,7 +106,7 @@
String title;
try (BufferedReader titleReader =
- new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8))) {
+ new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath()), UTF_8))) {
title = titleReader.readLine();
if (title != null && title.startsWith("[[")) {
// Generally the first line of the txt is the title. In a few cases the
diff --git a/lib/prolog/java/BuckPrologCompiler.java b/lib/prolog/java/BuckPrologCompiler.java
index d3f41c0..cc3e39e 100644
--- a/lib/prolog/java/BuckPrologCompiler.java
+++ b/lib/prolog/java/BuckPrologCompiler.java
@@ -15,9 +15,9 @@
import com.googlecode.prolog_cafe.compiler.Compiler;
import com.googlecode.prolog_cafe.exceptions.CompileException;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -46,7 +46,7 @@
private static void jar(File jar, File classes) throws IOException {
File tmp = File.createTempFile("prolog", ".jar", tmpdir);
try {
- try (JarOutputStream out = new JarOutputStream(new FileOutputStream(tmp))) {
+ try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(tmp.toPath()))) {
add(out, classes, "");
}
if (!tmp.renameTo(jar)) {
@@ -70,7 +70,7 @@
}
JarEntry e = new JarEntry(prefix + name);
- try (FileInputStream in = new FileInputStream(f)) {
+ try (InputStream in = Files.newInputStream(f.toPath())) {
e.setTime(f.lastModified());
out.putNextEntry(e);
byte[] buf = new byte[16 << 10];
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 0fdbd44..3fd774d 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -95,8 +95,10 @@
./polygerrit-ui/app/run_test.sh
```
-To allow the tests to run in Safari it is necessary to enable the
-"Allow Remote Automation" option under the "Develop" menu.
+To allow the tests to run in Safari:
+
+* In the Advanced preferences tab, check "Show Develop menu in menu bar".
+* In the Develop menu, enable the "Allow Remote Automation" option.
If you need to pass additional arguments to `wct`:
diff --git a/polygerrit-ui/.eslintrc.json b/polygerrit-ui/app/.eslintrc.json
similarity index 100%
rename from polygerrit-ui/.eslintrc.json
rename to polygerrit-ui/app/.eslintrc.json
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 7c12fa2..4e99272 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -34,10 +34,9 @@
name = "closure_lib",
srcs = ["gr-app.js"],
convention = "GOOGLE",
- language = "ECMASCRIPT6",
- suppress = [
- "JSC_BAD_JSDOC_ANNOTATION",
- ],
+ # TODO(davido): Clean up these issues: http://paste.openstack.org/show/608548
+ # and remove this supression
+ suppress = ["JSC_UNUSED_LOCAL_ASSIGNMENT"],
deps = [
"//lib/polymer_externs:polymer_closure",
"@io_bazel_rules_closure//closure/library",
@@ -52,6 +51,7 @@
defs = [
"--polymer_pass",
"--jscomp_off=duplicate",
+ "--force_inject_library=es6_runtime",
],
language = "ECMASCRIPT5",
deps = [":closure_lib"],
@@ -157,3 +157,18 @@
"manual",
],
)
+
+sh_test(
+ name = "lint_test",
+ size = "large",
+ srcs = ["lint_test.sh"],
+ data = [
+ ":pg_code",
+ ".eslintrc.json",
+ ],
+ # Should not run sandboxed.
+ tags = [
+ "local",
+ "manual",
+ ],
+)
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index 8807917..1c3642d 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -17,6 +17,17 @@
(function(window) {
'use strict';
+ // Tags identifying ChangeMessages that move change into WIP state.
+ var WIP_TAGS = [
+ 'autogenerated:gerrit:newWipPatchSet',
+ 'autogenerated:gerrit:setWorkInProgress',
+ ];
+
+ // Tags identifying ChangeMessages that move change out of WIP state.
+ var READY_TAGS = [
+ 'autogenerated:gerrit:setReadyForReview',
+ ];
+
/** @polymerBehavior Gerrit.PatchSetBehavior */
var PatchSetBehavior = {
/**
@@ -37,7 +48,23 @@
}
},
+ /**
+ * Construct a chronological list of patch sets derived from change details.
+ * Each element of this list is an object with the following properties:
+ *
+ * * num {number} The number identifying the patch set
+ * * desc {!string} Optional patch set description
+ * * wip {boolean} If true, this patch set was never subject to review.
+ *
+ * The wip property is determined by the change's current work_in_progress
+ * property and its log of change messages.
+ *
+ * @param {Object} change The change details
+ * @return {Array<Object>} Sorted list of patch set objects, as described
+ * above
+ */
computeAllPatchSets: function(change) {
+ if (!change) { return []; }
var patchNums = [];
for (var commit in change.revisions) {
if (change.revisions.hasOwnProperty(commit)) {
@@ -47,10 +74,45 @@
});
}
}
- return patchNums.sort(function(a, b) { return a.num - b.num; });
+ patchNums.sort(function(a, b) { return a.num - b.num; });
+ return this._computeWipForPatchSets(change, patchNums);
+ },
+
+ /**
+ * Populate the wip properties of the given list of patch sets.
+ *
+ * @param {Object} change The change details
+ * @param {Array<Object>} patchNums Sorted list of patch set objects, as
+ * generated by computeAllPatchSets
+ * @return {Array<Object>} The given list of patch set objects, with the
+ * wip property set on each of them
+ */
+ _computeWipForPatchSets: function(change, patchNums) {
+ if (!change.messages || !change.messages.length) {
+ return patchNums;
+ }
+ var psWip = {};
+ var wip = change.work_in_progress;
+ for (var i = 0; i < change.messages.length; i++) {
+ var msg = change.messages[i];
+ if (WIP_TAGS.indexOf(msg.tag) != -1) {
+ wip = true;
+ } else if (READY_TAGS.indexOf(msg.tag) != -1) {
+ wip = false;
+ }
+ if (psWip[msg._revision_number] !== false) {
+ psWip[msg._revision_number] = wip;
+ }
+ }
+
+ for (var i = 0; i < patchNums.length; i++) {
+ patchNums[i].wip = psWip[patchNums[i].num];
+ }
+ return patchNums;
},
computeLatestPatchNum: function(allPatchSets) {
+ if (!allPatchSets || !allPatchSets.length) { return undefined; }
return allPatchSets[allPatchSets.length - 1].num;
},
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index 892d94b..87e6455 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -79,5 +79,87 @@
done();
});
});
+
+ test('_computeWipForPatchSets', function() {
+ // Compute patch sets for a given timeline on a change. The initial WIP
+ // property of the change can be true or false. The map of tags by
+ // revision is keyed by patch set number. Each value is a list of change
+ // message tags in the order that they occurred in the timeline. These
+ // indicate actions that modify the WIP property of the change and/or
+ // create new patch sets.
+ //
+ // Returns the actual results with an assertWip method that can be used
+ // to compare against an expected value for a particular patch set.
+ function compute(initialWip, tagsByRevision) {
+ var change = {
+ messages: [],
+ work_in_progress: initialWip,
+ };
+ var revs = Object.keys(tagsByRevision).sort(function(a, b) {
+ return a - b;
+ });
+ revs.forEach(function(rev) {
+ tagsByRevision[rev].forEach(function(tag) {
+ change.messages.push({
+ tag: tag,
+ _revision_number: rev,
+ });
+ });
+ });
+ var patchNums = revs.map(function(rev) { return {num: rev}; });
+ patchNums = Gerrit.PatchSetBehavior._computeWipForPatchSets(
+ change, patchNums);
+ var actualWipsByRevision = {};
+ patchNums.forEach(function(patchNum) {
+ actualWipsByRevision[patchNum.num] = patchNum.wip;
+ });
+ var verifier = {
+ assertWip: function(revision, expectedWip) {
+ var patchNum = patchNums.find(function(patchNum) {
+ return patchNum.num == revision;
+ });
+ if (!patchNum) {
+ assert.fail('revision ' + revision + ' not found');
+ }
+ assert.equal(patchNum.wip, expectedWip,
+ 'wip state for ' + revision + ' is ' +
+ patchNum.wip + '; expected ' + expectedWip);
+ return verifier;
+ },
+ };
+ return verifier;
+ }
+
+ compute(false, {1: ['upload']}).assertWip(1, false);
+ compute(true, {1: ['upload']}).assertWip(1, true);
+
+ var setWip = 'autogenerated:gerrit:setWorkInProgress';
+ var uploadInWip = 'autogenerated:gerrit:newWipPatchSet';
+ var clearWip = 'autogenerated:gerrit:setReadyForReview';
+
+ compute(false, {
+ 1: ['upload', setWip],
+ 2: ['upload'],
+ 3: ['upload', clearWip],
+ 4: ['upload', setWip],
+ }).assertWip(1, false) // Change was created with PS1 ready for review
+ .assertWip(2, true) // PS2 was uploaded during WIP
+ .assertWip(3, false) // PS3 was marked ready for review after upload
+ .assertWip(4, false); // PS4 was uploaded ready for review
+
+ compute(false, {
+ 1: [uploadInWip, null, 'addReviewer'],
+ 2: ['upload'],
+ 3: ['upload', clearWip, setWip],
+ 4: ['upload'],
+ 5: ['upload', clearWip],
+ 6: [uploadInWip],
+ }).assertWip(1, true) // Change was created in WIP
+ .assertWip(2, true) // PS2 was uploaded during WIP
+ .assertWip(3, false) // PS3 was marked ready for review
+ .assertWip(4, true) // PS4 was uploaded during WIP
+ .assertWip(5, false) // PS5 was marked ready for review
+ .assertWip(6, true); // PS6 was uploaded with WIP option
+ });
});
</script>
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
index fa8289f..f9c4a80 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
@@ -20,8 +20,8 @@
/** @polymerBehavior Gerrit.PathListBehavior */
var PathListBehavior = {
specialFilePathCompare: function(a, b) {
- var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
// The commit message always goes first.
+ var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
if (a === COMMIT_MESSAGE_PATH) {
return -1;
}
@@ -29,6 +29,15 @@
return 1;
}
+ // The merge list always comes next.
+ var MERGE_LIST_PATH = '/MERGE_LIST';
+ if (a === MERGE_LIST_PATH) {
+ return -1;
+ }
+ if (b === MERGE_LIST_PATH) {
+ return 1;
+ }
+
var aLastDotIndex = a.lastIndexOf('.');
var aExt = a.substr(aLastDotIndex + 1);
var aFile = a.substr(0, aLastDotIndex) || a;
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index adf0bf1..164700f 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -27,13 +27,14 @@
var sort = Gerrit.PathListBehavior.specialFilePathCompare;
var testFiles = [
'/a.h',
+ '/MERGE_LIST',
'/a.cpp',
'/COMMIT_MSG',
'/asdasd',
'/mrPeanutbutter.py'
];
assert.deepEqual(testFiles.sort(sort),
- ['/COMMIT_MSG', '/a.h', '/a.cpp', '/asdasd', '/mrPeanutbutter.py']);
+ ['/COMMIT_MSG', '/MERGE_LIST', '/a.h', '/a.cpp', '/asdasd', '/mrPeanutbutter.py']);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 31721a0..bef92ed 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -51,6 +51,7 @@
RESTORE: 'restore',
REVERT: 'revert',
UNIGNORE: 'unignore',
+ WIP: 'wip',
};
// TODO(andybons): Add the rest of the revision actions.
@@ -596,6 +597,9 @@
case ChangeActions.DELETE:
this._handleDeleteTap();
break;
+ case ChangeActions.WIP:
+ this._handleWipTap();
+ break;
default:
this._fireAction(this._prependSlash(key), this.actions[key], false);
}
@@ -786,6 +790,9 @@
page.show(this.changePath(this.changeNum));
}
break;
+ case ChangeActions.WIP:
+ page.show(this.changePath(this.changeNum));
+ break;
default:
this.dispatchEvent(new CustomEvent('reload-change',
{detail: {action: action.__key}, bubbles: false}));
@@ -850,6 +857,10 @@
this._showActionDialog(this.$.confirmDeleteDialog);
},
+ _handleWipTap: function() {
+ this._fireAction('/wip', this.actions.wip, false);
+ },
+
/**
* Merge sources of change actions into a single ordered array of action
* values.
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 778b09c..7408e04 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -255,6 +255,9 @@
<section class="labelStatus">
<span class="title">Label Status</span>
<span class="value">
+ <div hidden$="[[!change.work_in_progress]]">
+ Work in progress
+ </div>
<div hidden$="[[!_showMissingLabels(change.labels)]]">
[[_computeMissingLabelsHeader(change.labels)]]
<ul id="missingLabels">
@@ -265,7 +268,7 @@
</template>
</ul>
</div>
- <div hidden$="[[_showMissingLabels(change.labels)]]">
+ <div hidden$="[[_showMissingRequirements(change.labels, change.work_in_progress)]]">
Ready to submit
</div>
</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 58938af..9046d1b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -242,6 +242,10 @@
return !!this._computeMissingLabels(labels).length;
},
+ _showMissingRequirements: function(labels, workInProgress) {
+ return workInProgress || this._showMissingLabels(labels);
+ },
+
_computeProjectURL: function(project) {
return this.getBaseUrl() + '/q/project:' +
this.encodeURL(project, false);
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index c531c79..0ac8531 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -88,6 +88,17 @@
});
test('computes submit status', function() {
+ var showMissingLabels = false;
+ sandbox.stub(element, '_showMissingLabels', function() {
+ return showMissingLabels;
+ });
+ assert.isFalse(element._showMissingRequirements(null, false));
+ assert.isTrue(element._showMissingRequirements(null, true));
+ showMissingLabels = true;
+ assert.isTrue(element._showMissingRequirements(null, false));
+ });
+
+ test('show missing labels', function() {
var labels = {};
assert.isFalse(element._showMissingLabels(labels));
labels = {test: {}};
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 2c89e5c..2d57392 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -19,6 +19,7 @@
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -68,11 +69,19 @@
transition: box-shadow 250ms linear;
width: 100%;
}
+ .header.wip {
+ background-color: #fcfad6;
+ border-bottom: 1px solid #ddd;
+ margin-bottom: .5em;
+ }
.header-title {
flex: 1;
font-size: 1.2em;
font-weight: bold;
}
+ .prefsButton {
+ float: right;
+ }
gr-change-star {
margin-right: .25em;
vertical-align: -.425em;
@@ -188,6 +197,9 @@
height: 0;
margin-bottom: 1em;
}
+ #diffPrefsContainer {
+ margin: auto 0 auto auto;
+ }
.patchInfo-header-wrapper {
width: 100%;
}
@@ -277,7 +289,7 @@
</style>
<div class="container loading" hidden$="[[!_loading]]">Loading...</div>
<div class="container" hidden$="{{_loading}}">
- <div class="header">
+ <div class$="[[_computeHeaderClass(_change)]]">
<span class="header-title">
<gr-change-star
id="changeStar"
@@ -301,6 +313,7 @@
--></template><!--
-->)<!--
--></template><!--
+ --><span hidden$="[[!_change.work_in_progress]]"> (Work in progress)</span><!--
-->: [[_change.subject]]
</span>
</div>
@@ -425,6 +438,7 @@
[[patchNum.num]]
/
[[computeLatestPatchNum(_allPatchSets)]]
+ [[_computePatchSetCommentsString(_comments, patchNum.num)]]
[[_computePatchSetDescription(_change, patchNum.num)]]
</option>
</template>
@@ -454,9 +468,17 @@
read-only="[[_descriptionReadOnly]]"
on-changed="_handleDescriptionChanged"></gr-editable-label>
</span>
+ <span id="diffPrefsContainer"
+ hidden$="[[_computePrefsButtonHidden(_diffPrefs, _loggedIn)]]"
+ hidden>
+ <gr-button link
+ class="prefsButton desktop"
+ on-tap="_handlePrefsTap">Diff Preferences</gr-button>
+ </span>
</div>
</div>
<gr-file-list id="fileList"
+ diff-prefs="{{_diffPrefs}}"
change="[[_change]]"
change-num="[[_changeNum]]"
patch-range="{{_patchRange}}"
@@ -500,10 +522,12 @@
diff-drafts="[[_diffDrafts]]"
server-config="[[serverConfig]]"
project-config="[[_projectConfig]]"
+ can-be-started="[[_canStartReview]]"
on-send="_handleReplySent"
on-cancel="_handleReplyCancel"
on-autogrow="_handleReplyAutogrow"
- hidden$="[[!_loggedIn]]">Reply</gr-reply-dialog>
+ hidden$="[[!_loggedIn]]">
+ </gr-reply-dialog>
</gr-overlay>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 1a7765c..ea835e7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -73,6 +73,7 @@
type: Object,
value: function() { return document.body; },
},
+ _diffPrefs: Object,
_numFilesShown: {
type: Number,
observer: '_numFilesShownChanged',
@@ -81,6 +82,10 @@
type: Object,
value: {},
},
+ _canStartReview: {
+ type: Boolean,
+ computed: '_computeCanStartReview(_loggedIn, _change, _account)',
+ },
_comments: Object,
_change: {
type: Object,
@@ -135,7 +140,7 @@
_replyButtonLabel: {
type: String,
value: 'Reply',
- computed: '_computeReplyButtonLabel(_diffDrafts.*)',
+ computed: '_computeReplyButtonLabel(_diffDrafts.*, _canStartReview)',
},
_selectedPatchSet: String,
_initialLoadComplete: {
@@ -186,6 +191,7 @@
'u': '_handleUKey',
'x': '_handleXKey',
'z': '_handleZKey',
+ ',': '_handleCommaKey',
},
attached: function() {
@@ -221,6 +227,10 @@
}
},
+ _computePrefsButtonHidden: function(prefs, loggedIn) {
+ return !loggedIn || !prefs;
+ },
+
_handleEditCommitMessage: function(e) {
this._editingCommitMessage = true;
this.$.commitMessageEditor.focusTextarea();
@@ -269,6 +279,11 @@
return false;
},
+ _handlePrefsTap: function(e) {
+ e.preventDefault();
+ this.$.fileList.openDiffPrefs();
+ },
+
_handleCommentSave: function(e) {
if (!e.target.comment.__draft) { return; }
@@ -562,7 +577,7 @@
},
_changeChanged: function(change) {
- if (!change) { return; }
+ if (!change || !this._patchRange || !this._allPatchSets) { return; }
this.set('_patchRange.basePatchNum',
this._patchRange.basePatchNum || 'PARENT');
this.set('_patchRange.patchNum',
@@ -718,7 +733,11 @@
return result;
},
- _computeReplyButtonLabel: function(changeRecord) {
+ _computeReplyButtonLabel: function(changeRecord, canStartReview) {
+ if (canStartReview) {
+ return 'Start review';
+ }
+
var drafts = (changeRecord && changeRecord.base) || {};
var draftCount = Object.keys(drafts).reduce(function(count, file) {
return count + drafts[file].length;
@@ -792,6 +811,14 @@
this.$.messageList.handleExpandCollapse(false);
},
+ _handleCommaKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e) ||
+ this.modifierPressed(e)) { return; }
+
+ e.preventDefault();
+ this.$.fileList.openDiffPrefs();
+ },
+
_determinePageBack: function() {
// Default backPage to '/' if user came to change view page
// via an email link, etc.
@@ -1030,6 +1057,27 @@
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
},
+ _computePatchSetCommentsString: function(allComments, patchNum) {
+ var numComments = 0;
+ var numUnresolved = 0;
+ for (var file in allComments) {
+ var comments = allComments[file];
+ numComments += this.$.fileList.getCommentsForPath(
+ allComments, patchNum, file).length;
+ numUnresolved += this.$.fileList.computeUnresolvedNum(
+ allComments, {}, patchNum, file);
+ }
+ var commentsStr = '';
+ if (numComments > 0) {
+ commentsStr = '(' + numComments + ' comments';
+ if (numUnresolved > 0) {
+ commentsStr += ', ' + numUnresolved + ' unresolved';
+ }
+ commentsStr += ')';
+ }
+ return commentsStr;
+ },
+
_computeDescriptionPlaceholder: function(readOnly) {
return (readOnly ? 'No' : 'Add a') + ' patch set description';
},
@@ -1063,6 +1111,11 @@
}
},
+ _computeCanStartReview: function(loggedIn, change, account) {
+ return !!(loggedIn && change.work_in_progress &&
+ change.owner._account_id === account._account_id);
+ },
+
_computeDescriptionReadOnly: function(loggedIn, change, account) {
return !(loggedIn && (account._account_id === change.owner._account_id));
},
@@ -1177,10 +1230,14 @@
this._updateCheckTimerHandle = this.async(function() {
this.fetchIsLatestKnown(this._change, this.$.restAPI)
.then(function(latest) {
- if (!latest) {
+ if (latest) {
+ this._startUpdateCheckTimer();
+ } else {
this._cancelUpdateCheckTimer();
this.fire('show-alert', {
message: 'A newer patch has been uploaded.',
+ // Persist this alert.
+ dismissOnNavigation: true,
action: 'Reload',
callback: function() {
// Load the current change without any patch range.
@@ -1189,7 +1246,6 @@
}.bind(this),
});
}
- this._startUpdateCheckTimer();
}.bind(this));
}, this.serverConfig.change.update_delay * 1000);
},
@@ -1206,5 +1262,9 @@
this._startUpdateCheckTimer();
}
},
+
+ _computeHeaderClass: function(change) {
+ return change.work_in_progress ? 'header wip' : 'header';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 59b3d41..5e932f8d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -98,6 +98,7 @@
test('A toggles overlay when logged in', function(done) {
sandbox.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ element._change = {labels: {}};
MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
flush(function() {
assert.isTrue(element.$.replyOverlay.opened);
@@ -156,6 +157,45 @@
MockInteractions.pressAndReleaseKeyOn(element, 68, null, 'd');
assert.isTrue(stub.called);
});
+
+ test(', should open diff preferences', function() {
+ var stub = sandbox.stub(element.$.fileList.$.diffPreferences, 'open');
+ MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
+ assert.isTrue(stub.called);
+ });
+ });
+
+ test('Diff preferences hidden when no prefs or logged out',
+ function() {
+ element._loggedIn = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = false;
+ element._diffPrefs = {'font_size': '12'};
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.diffPrefsContainer.hidden);
+ });
+
+ test('prefsButton opens gr-diff-preferences', function() {
+ var handlePrefsTapSpy = sandbox.spy(element, '_handlePrefsTap');
+ var overlayOpenStub = sandbox.stub(element.$.fileList,
+ 'openDiffPrefs');
+ var prefsButton = Polymer.dom(element.root).querySelectorAll(
+ '.prefsButton')[0];
+
+ MockInteractions.tap(prefsButton);
+
+ assert.isTrue(handlePrefsTapSpy.called);
+ assert.isTrue(overlayOpenStub.called);
});
test('_computeDescriptionReadOnly', function() {
@@ -197,6 +237,45 @@
assert.equal(result, 'R=\u200Btest@google.com\nR=\u200Btest@google.com');
}),
+ test('_computePatchSetCommentsString', function() {
+ // Test string with unresolved comments.
+ comments = {
+ 'foo': 'foo comments',
+ 'bar': 'bar comments',
+ 'xyz': 'xyz comments',
+ };
+ sandbox.stub(element.$.fileList, 'getCommentsForPath', function(c, p, f) {
+ if (f == 'foo') {
+ return ['comment1', 'comment2'];
+ } else if (f == 'bar') {
+ return ['comment1'];
+ } else {
+ return [];
+ }
+ });
+ sandbox.stub(
+ element.$.fileList, 'computeUnresolvedNum', function (c, d, p, f) {
+ if (f == 'foo') {
+ return 0;
+ } else if (f == 'bar') {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ assert.equal(element._computePatchSetCommentsString(comments, 1),
+ '(3 comments, 1 unresolved)');
+
+ // Test string with no unresolved comments.
+ delete comments['bar']
+ assert.equal(element._computePatchSetCommentsString(comments, 1),
+ '(2 comments)');
+
+ // Test string with no comments.
+ delete comments['foo']
+ assert.equal(element._computePatchSetCommentsString(comments, 1), '');
+ });
+
test('_handleDescriptionChanged', function() {
var putDescStub = sandbox.stub(element.$.restAPI, 'setDescription')
.returns(Promise.resolve({ok: true}));
@@ -309,21 +388,28 @@
});
test('reply button has updated count when there are drafts', function() {
- var replyButton = element.$$('gr-button.reply');
- assert.ok(replyButton);
- assert.equal(replyButton.textContent, 'Reply');
+ var getLabel = element._computeReplyButtonLabel;
- element._diffDrafts = null;
- assert.equal(replyButton.textContent, 'Reply');
+ assert.equal(getLabel(null, false), 'Reply');
+ assert.equal(getLabel(null, true), 'Start review');
- element._diffDrafts = {};
- assert.equal(replyButton.textContent, 'Reply');
+ var changeRecord = {base: null};
+ assert.equal(getLabel(changeRecord, false), 'Reply');
- element._diffDrafts = {
+ changeRecord.base = {};
+ assert.equal(getLabel(changeRecord, false), 'Reply');
+
+ changeRecord.base = {
'file1.txt': [{}],
'file2.txt': [{}, {}],
};
- assert.equal(replyButton.textContent, 'Reply (3)');
+ assert.equal(getLabel(changeRecord, false), 'Reply (3)');
+ });
+
+ test('start review button when owner of WIP change', function() {
+ assert.equal(
+ element._computeReplyButtonLabel(null, true),
+ 'Start review');
});
test('comment events properly update diff drafts', function() {
@@ -940,6 +1026,7 @@
suite('reply dialog tests', function() {
setup(function() {
sandbox.stub(element.$.replyDialog, '_draftChanged');
+ element._change = {labels: {}};
});
test('reply from comment adds quote text', function() {
@@ -1144,6 +1231,27 @@
element.serverConfig = {change: {update_delay: 12345}};
});
});
+
+ test('canStartReview computation', function() {
+ var account1 = {_account_id: 1};
+ var account2 = {_account_id: 2};
+ var change = {
+ owner: {_account_id: 1},
+ };
+ assert.isFalse(element._computeCanStartReview(true, change, account1));
+ change.work_in_progress = false;
+ assert.isFalse(element._computeCanStartReview(true, change, account1));
+ change.work_in_progress = true;
+ assert.isTrue(element._computeCanStartReview(true, change, account1));
+ assert.isFalse(element._computeCanStartReview(false, change, account1));
+ assert.isFalse(element._computeCanStartReview(true, change, account2));
+ });
+
+ test('header class computation', function() {
+ assert.equal(element._computeHeaderClass({}), 'header');
+ assert.equal(element._computeHeaderClass({work_in_progress: true}),
+ 'header wip');
+ });
});
});
</script>
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 0e640a7..8ad2c19 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
@@ -53,7 +53,9 @@
</style>
<template is="dom-repeat" items="[[_computeFilesFromComments(comments)]]" as="file">
<div class="file">
- <a href$="[[_computeFileDiffURL(file, changeNum, patchNum)]]">[[file]]</a>:
+ <a href$="[[_computeFileDiffURL(file, changeNum, patchNum)]]">
+ [[_computeFileDisplayName(file)]]
+ </a>:
</div>
<template is="dom-repeat"
items="[[_computeCommentsForFile(comments, file)]]" as="comment">
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 f1fb0fd..98a2508 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
@@ -14,6 +14,9 @@
(function() {
'use strict';
+ var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+ var MERGE_LIST_PATH = '/MERGE_LIST';
+
Polymer({
is: 'gr-comment-list',
@@ -39,6 +42,15 @@
'/' + patchNum + '/' + file;
},
+ _computeFileDisplayName: function(path) {
+ if (path === COMMIT_MESSAGE_PATH) {
+ return 'Commit message';
+ } else if (path === MERGE_LIST_PATH) {
+ return 'Merge list';
+ }
+ return path;
+ },
+
_isOnParent: function(comment) {
return comment.side === 'PARENT';
},
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index 34f2951..e27bad0 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -65,6 +65,15 @@
assert.equal(actual, expected);
});
+ test('_computeFileDisplayName', function() {
+ assert.equal(element._computeFileDisplayName('/COMMIT_MSG'),
+ 'Commit message');
+ assert.equal(element._computeFileDisplayName('/MERGE_LIST'),
+ 'Merge list');
+ assert.equal(element._computeFileDisplayName('/foo/bar/baz'),
+ '/foo/bar/baz');
+ });
+
test('_computeDiffLineURL', function() {
var comment = {line: 123, side: 'REVISION', patch_set: 10};
var expected = '/c/<change>/<patch>/<file>#123';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 2bcbac3..af412db 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -72,6 +72,7 @@
background-color: #ebf5fb;
}
.path {
+ cursor: pointer;
flex: 1;
padding-left: .35em;
text-decoration: none;
@@ -222,10 +223,11 @@
<option value="PARENT">Base</option>
<template
is="dom-repeat"
- items="[[_computePatchSets(revisions.*, patchRange.*)]]"
+ items="[[computeAllPatchSets(change)]]"
as="patchNum">
- <option value$="[[patchNum.num]]"
- disabled$="[[_computePatchSetDisabled(patchNum.num, patchRange.patchNum)]]">
+ <option
+ disabled$="[[_computePatchSetDisabled(patchNum.num, patchRange.patchNum)]]"
+ value$="[[patchNum.num]]">
[[patchNum.num]]
[[patchNum.desc]]
</option>
@@ -241,10 +243,9 @@
as="file"
initial-count="[[_fileListIncrement]]"
target-framerate="1">
- <div class="file-row row">
+ <div class="file-row row" data-path$="[[file.__path]]">
<div class="reviewed" hidden$="[[!_loggedIn]]" hidden>
<input type="checkbox" checked="[[file.isReviewed]]"
- data-path$="[[file.__path]]"
class="reviewed" aria-label="Reviewed checkbox">
</div>
<div class$="[[_computeClass('status', file.__path)]]"
@@ -252,22 +253,24 @@
aria-label$="[[_computeFileStatusLabel(file.status)]]">
[[_computeFileStatus(file.status)]]
</div>
- <a class$="[[_computePathClass(file.__path, _expandedFilePaths.*)]]"
- href$="[[_computeDiffURL(changeNum, patchRange, file.__path)]]"
- data-path$="[[file.__path]]">
- <div title$="[[_computeFileDisplayName(file.__path)]]"
- class="fullFileName">
- [[_computeFileDisplayName(file.__path)]]
- </div>
- <div title$="[[_computeFileDisplayName(file.__path)]]"
- class="truncatedFileName">
- [[_computeTruncatedFileDisplayName(file.__path)]]
- </div>
+ <span
+ data-url="[[_computeDiffURL(changeNum, patchRange, file.__path)]]"
+ class$="[[_computePathClass(file.__path, _expandedFilePaths.*)]]">
+ <a href$="[[_computeDiffURL(changeNum, patchRange, file.__path)]]">
+ <span title$="[[_computeFileDisplayName(file.__path)]]"
+ class="fullFileName">
+ [[_computeFileDisplayName(file.__path)]]
+ </span>
+ <span title$="[[_computeFileDisplayName(file.__path)]]"
+ class="truncatedFileName">
+ [[_computeTruncatedFileDisplayName(file.__path)]]
+ </span>
+ </a>
<div class="oldPath" hidden$="[[!file.old_path]]" hidden
title$="[[file.old_path]]">
[[file.old_path]]
</div>
- </a>
+ </span>
<div class="comments desktop">
<span class="drafts">
[[_computeDraftsString(drafts, patchRange.patchNum, file.__path)]]
@@ -323,9 +326,10 @@
change-num="[[changeNum]]"
patch-range="[[patchRange]]"
path="[[file.__path]]"
- prefs="[[_diffPrefs]]"
+ prefs="[[diffPrefs]]"
project-config="[[projectConfig]]"
on-line-selected="_onLineSelected"
+ no-render-on-prefs-change
view-mode="[[_getDiffViewMode(diffViewMode, _userPrefs)]]"></gr-diff>
</template>
</template>
@@ -379,6 +383,10 @@
[[_computeShowAllText(_files)]]
</gr-button><!--
--></gr-tooltip-content>
+ <gr-diff-preferences
+ id="diffPreferences"
+ prefs="{{diffPrefs}}"
+ local-prefs="{{_localPrefs}}"></gr-diff-preferences>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
<gr-diff-cursor id="diffCursor"></gr-diff-cursor>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 2a166ab..30ccb76 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -18,6 +18,7 @@
var PATCH_DESC_MAX_LENGTH = 500;
var WARN_SHOW_ALL_THRESHOLD = 1000;
var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+ var MERGE_LIST_PATH = '/MERGE_LIST';
var FileStatus = {
A: 'Added',
@@ -53,6 +54,7 @@
diffViewMode: {
type: String,
notify: true,
+ observer: '_updateDiffPreferences',
},
_files: {
type: Array,
@@ -68,7 +70,11 @@
value: function() { return []; },
},
_diffAgainst: String,
- _diffPrefs: Object,
+ diffPrefs: {
+ type: Object,
+ notify: true,
+ observer: '_updateDiffPreferences',
+ },
_userPrefs: Object,
_localPrefs: Object,
_showInlineDiffs: Boolean,
@@ -157,7 +163,7 @@
this._localPrefs = this.$.storage.getPreferences();
promises.push(this._getDiffPreferences().then(function(prefs) {
- this._diffPrefs = prefs;
+ this.diffPrefs = prefs;
}.bind(this)));
promises.push(this._getPreferences().then(function(prefs) {
@@ -172,6 +178,10 @@
return Polymer.dom(this.root).querySelectorAll('gr-diff');
},
+ openDiffPrefs: function() {
+ this.$.diffPreferences.open();
+ },
+
_calculatePatchChange: function(files) {
var filesNoCommitMsg = files.filter(function(files) {
return files.__path !== '/COMMIT_MSG';
@@ -205,20 +215,6 @@
return this.$.restAPI.getPreferences();
},
- _computePatchSets: function(revisionRecord) {
- var revisions = revisionRecord.base;
- var patchNums = [];
- for (var commit in revisions) {
- if (revisions.hasOwnProperty(commit)) {
- patchNums.push({
- num: revisions[commit]._number,
- desc: revisions[commit].description,
- });
- }
- }
- return patchNums.sort(function(a, b) { return a.num - b.num; });
- },
-
_computePatchSetDisabled: function(patchNum, currentPatchNum) {
return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
},
@@ -245,6 +241,19 @@
this._patchRangeStr(patchRange), true));
},
+ _updateDiffPreferences: function() {
+ if (!this.diffs.length) { return; }
+ // Re-render all expanded diffs sequentially.
+ var timerName = 'Update ' + this._expandedFilePaths.length +
+ ' diffs with new prefs';
+ this._renderInOrder(this._expandedFilePaths, this.diffs,
+ this._expandedFilePaths.length)
+ .then(function() {
+ this.$.reporting.timeEnd(timerName);
+ this.$.diffCursor.handleDiffUpdate();
+ }.bind(this));
+ },
+
_forEachDiff: function(fn) {
var diffs = this.diffs;
for (var i = 0; i < diffs.length; i++) {
@@ -293,7 +302,7 @@
return commentCount ? commentCount + 'c' : '';
},
- _getCommentsForPath: function(comments, patchNum, path) {
+ getCommentsForPath: function(comments, patchNum, path) {
return (comments[path] || []).filter(function(c) {
return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
});
@@ -302,7 +311,7 @@
_computeCountString: function(comments, patchNum, path, opt_noun) {
if (!comments) { return ''; }
- var patchComments = this._getCommentsForPath(comments, patchNum, path);
+ var patchComments = this.getCommentsForPath(comments, patchNum, path);
var num = patchComments.length;
if (num === 0) { return ''; }
if (!opt_noun) { return num; }
@@ -321,8 +330,14 @@
* @return {string}
*/
_computeUnresolvedString: function(comments, drafts, patchNum, path) {
- comments = this._getCommentsForPath(comments, patchNum, path);
- drafts = this._getCommentsForPath(drafts, patchNum, path);
+ var unresolvedNum = this.computeUnresolvedNum(
+ comments, drafts, patchNum, path);
+ return unresolvedNum === 0 ? '' : '(' + unresolvedNum + ' unresolved)';
+ },
+
+ computeUnresolvedNum: function(comments, drafts, patchNum, path) {
+ comments = this.getCommentsForPath(comments, patchNum, path);
+ drafts = this.getCommentsForPath(drafts, patchNum, path);
comments = comments.concat(drafts);
// Create an object where every comment ID is the key of an unresolved
@@ -345,18 +360,13 @@
return idMap[key];
});
- return unresolvedLeaves.length === 0 ?
- '' : '(' + unresolvedLeaves.length + ' unresolved)';
+ return unresolvedLeaves.length;
},
_computeReviewed: function(file, _reviewed) {
return _reviewed.indexOf(file.__path) !== -1;
},
- _handleReviewedChange: function(e) {
- this._reviewFile(Polymer.dom(e).rootTarget.getAttribute('data-path'));
- },
-
_reviewFile: function(path) {
var index = this._reviewed.indexOf(path);
var reviewed = index !== -1;
@@ -398,24 +408,38 @@
* have to get registered for potentially very long lists.
*/
_handleFileListTap: function(e) {
+ // Traverse upwards to find the row element if the target is not the row.
+ var row = e.target;
+ while (!row.classList.contains('row') && row.parentElement) {
+ row = row.parentElement;
+ }
+ var path = row.dataset.path;
+
// Handle checkbox mark as reviewed.
if (e.target.classList.contains('reviewed')) {
- return this._handleReviewedChange(e);
+ return this._reviewFile(path);
}
- // Check to see if the file should be expanded.
- var path = e.target.dataset.path || e.target.parentElement.dataset.path;
-
// If the user prefers to expand inline diffs rather than opening the diff
// view, intercept the click event.
if (!path || e.detail.sourceEvent.metaKey ||
e.detail.sourceEvent.ctrlKey) {
- return;
+ return;
}
+
if (e.target.dataset.expand ||
this._userPrefs && this._userPrefs.expand_inline_diffs) {
e.preventDefault();
this._togglePathExpanded(path);
+ return;
+ }
+
+ // If we clicked the row but not the link, then simulate a click on the
+ // anchor.
+ if (e.target.classList.contains('path') ||
+ e.target.classList.contains('oldPath')) {
+ var a = row.querySelector('a');
+ if (a) { a.click(); }
}
},
@@ -617,12 +641,16 @@
},
_computeFileDisplayName: function(path) {
- return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
+ if (path === COMMIT_MESSAGE_PATH) {
+ return 'Commit message';
+ } else if (path === MERGE_LIST_PATH) {
+ return 'Merge list';
+ }
+ return path;
},
_computeTruncatedFileDisplayName: function(path) {
- return path === COMMIT_MESSAGE_PATH ?
- 'Commit message' : util.truncatePath(path);
+ return util.truncatePath(this._computeFileDisplayName(path));
},
_formatBytes: function(bytes) {
@@ -652,7 +680,7 @@
_computeClass: function(baseClass, path) {
var classes = [baseClass];
- if (path === COMMIT_MESSAGE_PATH) {
+ if (path === COMMIT_MESSAGE_PATH || path === MERGE_LIST_PATH) {
classes.push('invisible');
}
return classes.join(' ');
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 4abd68a..d07f364 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -522,9 +522,13 @@
assert.equal(
element._computeUnresolvedString(comments, [], 2, 'myfile.txt'), '');
assert.equal(
+ element.computeUnresolvedNum(comments, [], 2, 'myfile.txt'), 0);
+ assert.equal(
element._computeUnresolvedString(comments, [], 2, 'unresolved.file'),
'(1 unresolved)');
assert.equal(
+ element.computeUnresolvedNum(comments, [], 2, 'unresolved.file'), 1);
+ assert.equal(
element._computeUnresolvedString(comments, drafts, 2,
'unresolved.file'), '');
});
@@ -590,8 +594,8 @@
{num: 3, desc: 'test'},
{num: 4, desc: 'test'},
];
- var patchNums = element._computePatchSets({
- base: {
+ var patchNums = element.computeAllPatchSets({
+ revisions: {
rev3: {_number: 3, description: 'test'},
rev1: {_number: 1, description: 'test'},
rev4: {_number: 4, description: 'test'},
@@ -620,10 +624,12 @@
basePatchNum: 'PARENT',
patchNum: '3',
};
- element.revisions = {
- rev1: {_number: 1},
- rev2: {_number: 2},
- rev3: {_number: 3},
+ element.change = {
+ revisions: {
+ rev1: {_number: 1},
+ rev2: {_number: 2},
+ rev3: {_number: 3},
+ },
};
flush(function() {
var selectEl = element.$.patchChange;
@@ -693,6 +699,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
+ sandbox.spy(element, '_updateDiffPreferences');
element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
@@ -708,6 +715,7 @@
assert.equal(diffDisplay.viewMode, 'SIDE_BY_SIDE');
element.set('diffViewMode', 'UNIFIED_DIFF');
assert.equal(diffDisplay.viewMode, 'UNIFIED_DIFF');
+ assert.isTrue(element._updateDiffPreferences.called);
});
test('diff mode selector initializes from preferences', function() {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index d08961c..d9cdcc0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -204,7 +204,7 @@
id="textarea"
class="message"
autocomplete="on"
- placeholder="Say something nice..."
+ placeholder=[[_messagePlaceholder]]
disabled="{{disabled}}"
rows="4"
max-rows="15"
@@ -248,7 +248,14 @@
primary
disabled="[[!_isState(knownLatestState, 'latest')]]"
class="action send"
- on-tap="_sendTapHandler">Send</gr-button>
+ on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
+ </gr-button>
+ <template is="dom-if" if="[[canBeStarted]]">
+ <gr-button
+ disabled="[[!_isState(knownLatestState, 'latest')]]"
+ class="action save"
+ on-tap="_saveTapHandler">Save</gr-button>
+ </template>
<span
id="checkingStatusLabel"
hidden$="[[!_isState(knownLatestState, 'checking')]]">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 0bb548b..79a1f34 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -65,6 +65,10 @@
properties: {
change: Object,
patchNum: String,
+ canBeStarted: {
+ type: Boolean,
+ value: false,
+ },
disabled: {
type: Boolean,
value: false,
@@ -90,6 +94,10 @@
serverConfig: Object,
projectConfig: Object,
knownLatestState: String,
+ underReview: {
+ type: Boolean,
+ value: true,
+ },
_account: Object,
_ccs: Array,
@@ -97,6 +105,10 @@
type: Object,
observer: '_reviewerPendingConfirmationUpdated',
},
+ _messagePlaceholder: {
+ type: String,
+ computed: '_computeMessagePlaceholder(canBeStarted)',
+ },
_owner: Object,
_pendingConfirmationDetails: Object,
_includeComments: {
@@ -120,6 +132,10 @@
REVIEWER: [],
},
},
+ _sendButtonLabel: {
+ type: String,
+ computed: '_computeSendButtonLabel(canBeStarted)',
+ },
},
FocusTarget: FocusTarget,
@@ -177,7 +193,8 @@
},
setLabelValue: function(label, value) {
- var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
+ var selectorEl =
+ this.$.labelScores.$$('iron-selector[data-label="' + label + '"]');
// The selector may not be present if it’s not at the latest patch set.
if (!selectorEl) { return; }
var item = selectorEl.$$('gr-button[data-value="' + value + '"]');
@@ -416,6 +433,12 @@
if (total > 1) { return total + ' Drafts'; }
},
+ _computeMessagePlaceholder: function(canBeStarted) {
+ return canBeStarted ?
+ 'Add a note for your reviewers...' :
+ 'Say something nice...';
+ },
+
_changeUpdated: function(changeRecord, owner, serverConfig) {
this._rebuildReviewerArrays(changeRecord.base, owner, serverConfig);
},
@@ -498,18 +521,39 @@
this.serverConfig);
},
- _sendTapHandler: function(e) {
+ _saveTapHandler: function(e) {
e.preventDefault();
this.send(this._includeComments).then(function(keepReviewers) {
this._purgeReviewersPendingRemove(false, keepReviewers);
}.bind(this));
},
+ _sendTapHandler: function(e) {
+ e.preventDefault();
+ if (this.canBeStarted) {
+ this._startReview()
+ .then(function() {
+ return this.send(this._includeComments);
+ }.bind(this))
+ .then(this._purgeReviewersPendingRemove.bind(this));
+ return;
+ }
+ this.send(this._includeComments)
+ .then(this._purgeReviewersPendingRemove.bind(this));
+ },
+
_saveReview: function(review, opt_errFn) {
return this.$.restAPI.saveChangeReview(this.change._number, this.patchNum,
review, opt_errFn);
},
+ _startReview: function() {
+ if (!this.canBeStarted) {
+ return Promise.resolve();
+ }
+ return this.$.restAPI.startReview(this.change._number);
+ },
+
_reviewerPendingConfirmationUpdated: function(reviewer) {
if (reviewer === null) {
this.$.reviewerConfirmationOverlay.close();
@@ -582,5 +626,9 @@
// Load the current change without any patch range.
location.href = this.getBaseUrl() + '/c/' + this.change._number;
},
+
+ _computeSendButtonLabel: function(canBeStarted) {
+ return canBeStarted ? "Start review" : "Send";
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 253f01c..55a2c9c 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -214,6 +214,17 @@
});
});
+ test('setlabelValue', function() {
+ element._account = {_account_id: 1};
+ flushAsynchronousOperations();
+ var label = 'Verified';
+ var value = '+1';
+ element.setLabelValue(label, value);
+ flushAsynchronousOperations();
+ var labels = element.$.labelScores.getLabelValues();
+ assert.deepEqual(labels, {'Verified': 1});
+ });
+
function getActiveElement() {
return Polymer.IronOverlayManager.deepActiveElement;
}
@@ -665,5 +676,23 @@
assert.isTrue(cancelHandler.called);
});
+
+ test('_computeMessagePlaceholder', function() {
+ assert.equal(
+ element._computeMessagePlaceholder(false),
+ 'Say something nice...');
+ assert.equal(
+ element._computeMessagePlaceholder(true),
+ 'Add a note for your reviewers...');
+ });
+
+ test('_computeSendButtonLabel', function() {
+ assert.equal(
+ element._computeSendButtonLabel(false),
+ 'Send');
+ assert.equal(
+ element._computeSendButtonLabel(true),
+ 'Start review');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 27dcd4a..1ccb30b 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -14,13 +14,13 @@
(function() {
'use strict';
- var HIDE_ALERT_TIMEOUT_MS = 5000;
- var CHECK_SIGN_IN_INTERVAL_MS = 60 * 1000;
- var STALE_CREDENTIAL_THRESHOLD_MS = 10 * 60 * 1000;
- var SIGN_IN_WIDTH_PX = 690;
- var SIGN_IN_HEIGHT_PX = 500;
- var TOO_MANY_FILES = 'too many files to find conflicts';
- var AUTHENTICATION_REQUIRED = 'Authentication required\n';
+ const HIDE_ALERT_TIMEOUT_MS = 5000;
+ const CHECK_SIGN_IN_INTERVAL_MS = 60 * 1000;
+ const STALE_CREDENTIAL_THRESHOLD_MS = 10 * 60 * 1000;
+ const SIGN_IN_WIDTH_PX = 690;
+ const SIGN_IN_HEIGHT_PX = 500;
+ const TOO_MANY_FILES = 'too many files to find conflicts';
+ const AUTHENTICATION_REQUIRED = 'Authentication required\n';
Polymer({
is: 'gr-error-manager',
@@ -48,11 +48,11 @@
*/
_lastCredentialCheck: {
type: Number,
- value: function() { return Date.now(); },
- }
+ value() { return Date.now(); },
+ },
},
- attached: function() {
+ attached() {
this.listen(document, 'server-error', '_handleServerError');
this.listen(document, 'network-error', '_handleNetworkError');
this.listen(document, 'show-alert', '_handleShowAlert');
@@ -60,7 +60,7 @@
this.listen(document, 'show-auth-required', '_handleAuthRequired');
},
- detached: function() {
+ detached() {
this._clearHideAlertHandle();
this.unlisten(document, 'server-error', '_handleServerError');
this.unlisten(document, 'network-error', '_handleNetworkError');
@@ -68,21 +68,21 @@
this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
},
- _shouldSuppressError: function(msg) {
- return msg.indexOf(TOO_MANY_FILES) > -1;
+ _shouldSuppressError(msg) {
+ return msg.includes(TOO_MANY_FILES);
},
- _handleAuthRequired: function() {
+ _handleAuthRequired() {
this._showAuthErrorAlert(
'Log in is required to perform that action.', 'Log in.');
},
- _handleServerError: function(e) {
+ _handleServerError(e) {
Promise.all([
- e.detail.response.text(), this._getLoggedIn()
- ]).then(function(values) {
- var text = values[0];
- var loggedIn = values[1];
+ e.detail.response.text(), this._getLoggedIn(),
+ ]).then(values => {
+ const text = values[0];
+ const loggedIn = values[1];
if (e.detail.response.status === 403 &&
loggedIn &&
text === AUTHENTICATION_REQUIRED) {
@@ -92,48 +92,58 @@
} else if (!this._shouldSuppressError(text)) {
this._showAlert('Server error: ' + text);
}
- }.bind(this));
+ });
},
- _handleShowAlert: function(e) {
- this._showAlert(e.detail.message, e.detail.action, e.detail.callback);
+ _handleShowAlert(e) {
+ this._showAlert(e.detail.message, e.detail.action, e.detail.callback,
+ e.detail.dismissOnNavigation);
},
- _handleNetworkError: function(e) {
+ _handleNetworkError(e) {
this._showAlert('Server unavailable');
console.error(e.detail.error.message);
},
- _getLoggedIn: function() {
+ _getLoggedIn() {
return this.$.restAPI.getLoggedIn();
},
- _showAlert: function(text, opt_actionText, opt_actionCallback) {
+ _showAlert(text, opt_actionText, opt_actionCallback,
+ dismissOnNavigation) {
if (this._alertElement) { return; }
this._clearHideAlertHandle();
- this._hideAlertHandle =
- this.async(this._hideAlert, HIDE_ALERT_TIMEOUT_MS);
- var el = this._createToastAlert();
+ if (dismissOnNavigation) {
+ // Persist alert until navigation.
+ this.listen(document, 'location-change', '_hideAlert');
+ } else {
+ this._hideAlertHandle =
+ this.async(this._hideAlert, HIDE_ALERT_TIMEOUT_MS);
+ }
+ const el = this._createToastAlert();
el.show(text, opt_actionText, opt_actionCallback);
this._alertElement = el;
},
- _hideAlert: function() {
+ _hideAlert() {
if (!this._alertElement) { return; }
this._alertElement.hide();
this._alertElement = null;
+
+ // Remove listener for page navigation, if it exists.
+ this.unlisten(document, 'location-change', '_hideAlert');
},
- _clearHideAlertHandle: function() {
+ _clearHideAlertHandle() {
if (this._hideAlertHandle != null) {
this.cancelAsync(this._hideAlertHandle);
this._hideAlertHandle = null;
}
},
- _showAuthErrorAlert: function(errorText, actionText) {
+ _showAuthErrorAlert(errorText, actionText) {
// TODO(viktard): close alert if it's not for auth error.
if (this._alertElement) { return; }
@@ -148,13 +158,13 @@
}
},
- _createToastAlert: function() {
- var el = document.createElement('gr-alert');
+ _createToastAlert() {
+ const el = document.createElement('gr-alert');
el.toast = true;
return el;
},
- _handleVisibilityChange: function() {
+ _handleVisibilityChange() {
// Ignore when the page is transitioning to hidden (or hidden is
// undefined).
if (document.hidden !== false) { return; }
@@ -162,7 +172,7 @@
// If not currently refreshing credentials and the credentials are old,
// request them to confirm their validity or (display an auth toast if it
// fails).
- var timeSinceLastCheck = Date.now() - this._lastCredentialCheck;
+ const timeSinceLastCheck = Date.now() - this._lastCredentialCheck;
if (!this._refreshingCredentials &&
this.knownAccountId !== undefined &&
timeSinceLastCheck > STALE_CREDENTIAL_THRESHOLD_MS) {
@@ -171,18 +181,17 @@
}
},
- _requestCheckLoggedIn: function() {
+ _requestCheckLoggedIn() {
this.debounce(
- 'checkLoggedIn', this._checkSignedIn, CHECK_SIGN_IN_INTERVAL_MS);
+ 'checkLoggedIn', this._checkSignedIn, CHECK_SIGN_IN_INTERVAL_MS);
},
- _checkSignedIn: function() {
- this.$.restAPI.checkCredentials().then(function(account) {
- var isLoggedIn = !!account;
+ _checkSignedIn() {
+ this.$.restAPI.checkCredentials().then(account => {
+ const isLoggedIn = !!account;
this._lastCredentialCheck = Date.now();
if (this._refreshingCredentials) {
if (isLoggedIn) {
-
// If the credentials were refreshed but the account is different
// then reload the page completely.
if (account._account_id !== this.knownAccountId) {
@@ -195,17 +204,19 @@
this._requestCheckLoggedIn();
}
}
- }.bind(this));
+ });
},
- _reloadPage: function() {
+ _reloadPage() {
window.location.reload();
},
- _createLoginPopup: function() {
- var left = window.screenLeft + (window.outerWidth - SIGN_IN_WIDTH_PX) / 2;
- var top = window.screenTop + (window.outerHeight - SIGN_IN_HEIGHT_PX) / 2;
- var options = [
+ _createLoginPopup() {
+ const left = window.screenLeft +
+ (window.outerWidth - SIGN_IN_WIDTH_PX) / 2;
+ const top = window.screenTop +
+ (window.outerHeight - SIGN_IN_HEIGHT_PX) / 2;
+ const options = [
'width=' + SIGN_IN_WIDTH_PX,
'height=' + SIGN_IN_HEIGHT_PX,
'left=' + left,
@@ -216,14 +227,14 @@
this.listen(window, 'focus', '_handleWindowFocus');
},
- _handleCredentialRefreshed: function() {
+ _handleCredentialRefreshed() {
this.unlisten(window, 'focus', '_handleWindowFocus');
this._refreshingCredentials = false;
this._hideAlert();
this._showAlert('Credentials refreshed.');
},
- _handleWindowFocus: function() {
+ _handleWindowFocus() {
this.flushDebouncer('checkLoggedIn');
},
});
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index c8743fb..ba10f09 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -33,69 +33,69 @@
</test-fixture>
<script>
- suite('gr-error-manager tests', function() {
- var element;
- var sandbox;
+ suite('gr-error-manager tests', () => {
+ let element;
+ let sandbox;
- setup(function() {
+ setup(() => {
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
- getLoggedIn: function() { return Promise.resolve(true); },
+ getLoggedIn() { return Promise.resolve(true); },
});
element = fixture('basic');
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
});
- test('does not show auth error on 403 by default', function(done) {
- var showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
- var responseText = Promise.resolve('server says no.');
+ test('does not show auth error on 403 by default', done => {
+ const showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
+ const responseText = Promise.resolve('server says no.');
element.fire('server-error',
- {response: {status: 403, text: function() { return responseText; }}}
+ {response: {status: 403, text() { return responseText; }}}
);
Promise.all([
element.$.restAPI.getLoggedIn.lastCall.returnValue,
responseText,
- ]).then(function() {
- assert.isFalse(showAuthErrorStub.calledOnce);
- done();
+ ]).then(() => {
+ assert.isFalse(showAuthErrorStub.calledOnce);
+ done();
});
});
- test('shows auth error on 403 and Authentication required', function(done) {
- var showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
- var responseText = Promise.resolve('Authentication required\n');
+ test('shows auth error on 403 and Authentication required', done => {
+ const showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
+ const responseText = Promise.resolve('Authentication required\n');
element.fire('server-error',
- {response: {status: 403, text: function() { return responseText; }}}
+ {response: {status: 403, text() { return responseText; }}}
);
Promise.all([
element.$.restAPI.getLoggedIn.lastCall.returnValue,
responseText,
- ]).then(function() {
+ ]).then(() => {
assert.isTrue(showAuthErrorStub.calledOnce);
done();
});
});
- test('show logged in error', function() {
+ test('show logged in error', () => {
sandbox.stub(element, '_showAuthErrorAlert');
element.fire('show-auth-required');
assert.isTrue(element._showAuthErrorAlert.calledWithExactly(
'Log in is required to perform that action.', 'Log in.'));
});
- test('show normal server error', function(done) {
- var showAlertStub = sandbox.stub(element, '_showAlert');
- var textSpy = sandbox.spy(function() { return Promise.resolve('ZOMG'); });
+ test('show normal server error', done => {
+ const showAlertStub = sandbox.stub(element, '_showAlert');
+ const textSpy = sandbox.spy(() => { return Promise.resolve('ZOMG'); });
element.fire('server-error', {response: {status: 500, text: textSpy}});
assert.isTrue(textSpy.called);
Promise.all([
element.$.restAPI.getLoggedIn.lastCall.returnValue,
textSpy.lastCall.returnValue,
- ]).then(function() {
+ ]).then(() => {
assert.isTrue(showAlertStub.calledOnce);
assert.isTrue(showAlertStub.lastCall.calledWithExactly(
'Server error: ZOMG'));
@@ -103,9 +103,9 @@
});
});
- test('suppress TOO_MANY_FILES error', function(done) {
- var showAlertStub = sandbox.stub(element, '_showAlert');
- var textSpy = sandbox.spy(function() {
+ test('suppress TOO_MANY_FILES error', done => {
+ const showAlertStub = sandbox.stub(element, '_showAlert');
+ const textSpy = sandbox.spy(() => {
return Promise.resolve('too many files to find conflicts');
});
element.fire('server-error', {response: {status: 500, text: textSpy}});
@@ -114,17 +114,17 @@
Promise.all([
element.$.restAPI.getLoggedIn.lastCall.returnValue,
textSpy.lastCall.returnValue,
- ]).then(function() {
+ ]).then(() => {
assert.isFalse(showAlertStub.called);
done();
});
});
- test('show network error', function(done) {
- var consoleErrorStub = sandbox.stub(console, 'error');
- var showAlertStub = sandbox.stub(element, '_showAlert');
+ test('show network error', done => {
+ const consoleErrorStub = sandbox.stub(console, 'error');
+ const showAlertStub = sandbox.stub(element, '_showAlert');
element.fire('network-error', {error: new Error('ZOMG')});
- flush(function() {
+ flush(() => {
assert.isTrue(showAlertStub.calledOnce);
assert.isTrue(showAlertStub.lastCall.calledWithExactly(
'Server unavailable'));
@@ -134,21 +134,21 @@
});
});
- test('show auth refresh toast', function(done) {
- var refreshStub = sandbox.stub(element.$.restAPI, 'checkCredentials',
- function() { return Promise.resolve(true); });
- var toastSpy = sandbox.spy(element, '_createToastAlert');
- var windowOpen = sandbox.stub(window, 'open');
- var responseText = Promise.resolve('Authentication required\n');
+ test('show auth refresh toast', done => {
+ const refreshStub = sandbox.stub(element.$.restAPI, 'checkCredentials',
+ () => { return Promise.resolve(true); });
+ const toastSpy = sandbox.spy(element, '_createToastAlert');
+ const windowOpen = sandbox.stub(window, 'open');
+ const responseText = Promise.resolve('Authentication required\n');
element.fire('server-error',
- {response: {status: 403, text: function() { return responseText; }}}
+ {response: {status: 403, text() { return responseText; }}}
);
Promise.all([
element.$.restAPI.getLoggedIn.lastCall.returnValue,
responseText,
- ]).then(function() {
+ ]).then(() => {
assert.isTrue(toastSpy.called);
- var toast = toastSpy.lastCall.returnValue;
+ let toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
assert.include(
Polymer.dom(toast.root).textContent, 'Auth error');
@@ -163,12 +163,12 @@
assert.equal(windowOpen.lastCall.args[2].indexOf('noopener=yes'),
-1);
- var hideToastSpy = sandbox.spy(toast, 'hide');
+ const hideToastSpy = sandbox.spy(toast, 'hide');
element._handleWindowFocus();
assert.isTrue(refreshStub.called);
element.flushDebouncer('checkLoggedIn');
- flush(function() {
+ flush(() => {
assert.isTrue(refreshStub.called);
assert.isTrue(hideToastSpy.called);
@@ -182,18 +182,18 @@
});
});
- test('show alert', function() {
- var alertObj = {message: 'foo'}
+ test('show alert', () => {
+ const alertObj = {message: 'foo'};
sandbox.stub(element, '_showAlert');
- element.fire('show-alert', {message: 'foo'});
+ element.fire('show-alert', alertObj);
assert.isTrue(element._showAlert.calledOnce);
assert.equal(element._showAlert.lastCall.args[0], 'foo');
assert.isNotOk(element._showAlert.lastCall.args[1]);
assert.isNotOk(element._showAlert.lastCall.args[2]);
});
- test('checks stale credentials on visibility change', function() {
- var refreshStub = sandbox.stub(element.$.restAPI,
+ test('checks stale credentials on visibility change', () => {
+ const refreshStub = sandbox.stub(element.$.restAPI,
'checkCredentials');
sandbox.stub(Date, 'now').returns(999999);
element._lastCredentialCheck = 0;
@@ -211,19 +211,19 @@
assert.equal(element._lastCredentialCheck, 999999);
});
- test('refresh loop continues on credential fail', function(done) {
- var accountPromise = Promise.resolve(null);
+ test('refresh loop continues on credential fail', done => {
+ const accountPromise = Promise.resolve(null);
sandbox.stub(element.$.restAPI, 'checkCredentials')
.returns(accountPromise);
- var requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
- var handleRefreshStub = sandbox.stub(element,
+ const requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
+ const handleRefreshStub = sandbox.stub(element,
'_handleCredentialRefreshed');
- var reloadStub = sandbox.stub(element, '_reloadPage');
+ const reloadStub = sandbox.stub(element, '_reloadPage');
element._refreshingCredentials = true;
element._checkSignedIn();
- accountPromise.then(function() {
+ accountPromise.then(() => {
assert.isTrue(requestCheckStub.called);
assert.isFalse(handleRefreshStub.called);
assert.isFalse(reloadStub.called);
@@ -231,20 +231,20 @@
});
});
- test('refreshes with same credentials', function(done) {
- var accountPromise = Promise.resolve({_account_id: 1234});
+ test('refreshes with same credentials', done => {
+ const accountPromise = Promise.resolve({_account_id: 1234});
sandbox.stub(element.$.restAPI, 'checkCredentials')
.returns(accountPromise);
- var requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
- var handleRefreshStub = sandbox.stub(element,
+ const requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
+ const handleRefreshStub = sandbox.stub(element,
'_handleCredentialRefreshed');
- var reloadStub = sandbox.stub(element, '_reloadPage');
+ const reloadStub = sandbox.stub(element, '_reloadPage');
element.knownAccountId = 1234;
element._refreshingCredentials = true;
element._checkSignedIn();
- accountPromise.then(function() {
+ accountPromise.then(() => {
assert.isFalse(requestCheckStub.called);
assert.isTrue(handleRefreshStub.called);
assert.isFalse(reloadStub.called);
@@ -252,25 +252,41 @@
});
});
- test('reloads when refreshed credentials differ', function(done) {
- var accountPromise = Promise.resolve({_account_id: 1234});
+ test('reloads when refreshed credentials differ', done => {
+ const accountPromise = Promise.resolve({_account_id: 1234});
sandbox.stub(element.$.restAPI, 'checkCredentials')
.returns(accountPromise);
- var requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
- var handleRefreshStub = sandbox.stub(element,
+ const requestCheckStub = sandbox.stub(element, '_requestCheckLoggedIn');
+ const handleRefreshStub = sandbox.stub(element,
'_handleCredentialRefreshed');
- var reloadStub = sandbox.stub(element, '_reloadPage');
+ const reloadStub = sandbox.stub(element, '_reloadPage');
element.knownAccountId = 4321; // Different from 1234
element._refreshingCredentials = true;
element._checkSignedIn();
- accountPromise.then(function() {
+ accountPromise.then(() => {
assert.isFalse(requestCheckStub.called);
assert.isFalse(handleRefreshStub.called);
assert.isTrue(reloadStub.called);
done();
});
});
+
+ test('dismissOnNavigation respected', () => {
+ const asyncStub = sandbox.stub(element, 'async');
+ const hideSpy = sandbox.spy(element, '_hideAlert');
+ // No async call when dismissOnNavigation supplied.
+ element._showAlert('test', null, null, true);
+ assert.isFalse(asyncStub.called);
+
+ // When page nav happens, clear alert.
+ document.dispatchEvent(new CustomEvent('location-change'));
+ assert.isTrue(hideSpy.called);
+
+ // When timeout is not supplied, use HIDE_ALERT_TIMEOUT_MS.
+ element._showAlert('test');
+ assert.isTrue(asyncStub.called);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 265e970..0473d66 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -40,6 +40,19 @@
.bigTitle:hover {
text-decoration: underline;
}
+ .bigTitle::before {
+ background-image: var(--header-icon);
+ background-size: var(--header-icon-size) var(--header-icon-size);
+ content: "";
+ display: inline-block;
+ height: var(--header-icon-size);
+ margin: 0 .25em 0 0;
+ vertical-align: text-bottom;
+ width: var(--header-icon-size);
+ }
+ .bigTitle::after {
+ content: var(--header-title-content);
+ }
ul {
list-style: none;
}
@@ -101,7 +114,7 @@
}
</style>
<nav>
- <a href$="[[_computeRelativeURL('/')]]" class="bigTitle">PolyGerrit</a>
+ <a href$="[[_computeRelativeURL('/')]]" class="bigTitle"></a>
<ul class="links">
<template is="dom-repeat" items="[[_links]]" as="linkGroup">
<li>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index ab042c4..ec57629 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -111,6 +111,7 @@
},
_docBaseUrl: {
type: String,
+ value: null,
},
_links: {
type: Array,
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 1f96014..2296567 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -15,7 +15,7 @@
'use strict';
// Latency reporting constants.
- var TIMING = {
+ const TIMING = {
TYPE: 'timing-report',
CATEGORY: 'UI Latency',
// Reported events - alphabetize below.
@@ -24,25 +24,25 @@
};
// Navigation reporting constants.
- var NAVIGATION = {
+ const NAVIGATION = {
TYPE: 'nav-report',
CATEGORY: 'Location Changed',
PAGE: 'Page',
};
- var ERROR = {
+ const ERROR = {
TYPE: 'error',
CATEGORY: 'exception',
};
- var INTERACTION_TYPE = 'interaction';
+ const INTERACTION_TYPE = 'interaction';
- var CHANGE_VIEW_REGEX = /^\/c\/\d+\/?\d*$/;
- var DIFF_VIEW_REGEX = /^\/c\/\d+\/\d+\/.+$/;
+ const CHANGE_VIEW_REGEX = /^\/c\/\d+\/?\d*$/;
+ const DIFF_VIEW_REGEX = /^\/c\/\d+\/\d+\/.+$/;
- var pending = [];
+ const pending = [];
- var onError = function(oldOnError, msg, url, line, column, error) {
+ const onError = function(oldOnError, msg, url, line, column, error) {
if (oldOnError) {
oldOnError(msg, url, line, column, error);
}
@@ -51,23 +51,23 @@
column = column || error.columnNumber;
msg = msg || error.toString();
}
- var payload = {
- url: url,
- line: line,
- column: column,
- error: error,
+ const payload = {
+ url,
+ line,
+ column,
+ error,
};
GrReporting.prototype.reporter(ERROR.TYPE, ERROR.CATEGORY, msg, payload);
return true;
};
- var catchErrors = function(opt_context) {
- var context = opt_context || window;
+ const catchErrors = function(opt_context) {
+ const context = opt_context || window;
context.onerror = onError.bind(null, context.onerror);
};
catchErrors();
- var GrReporting = Polymer({
+ const GrReporting = Polymer({
is: 'gr-reporting',
properties: {
@@ -75,7 +75,7 @@
_baselines: {
type: Array,
- value: function() { return {}; },
+ value() { return {}; },
},
},
@@ -87,24 +87,24 @@
return window.performance.timing;
},
- now: function() {
+ now() {
return Math.round(10 * window.performance.now()) / 10;
},
- reporter: function() {
- var report = (Gerrit._arePluginsLoaded() && !pending.length) ?
+ reporter(...args) {
+ const report = (Gerrit._arePluginsLoaded() && !pending.length) ?
this.defaultReporter : this.cachingReporter;
- report.apply(this, arguments);
+ report.apply(this, args);
},
- defaultReporter: function(type, category, eventName, eventValue) {
- var detail = {
- type: type,
- category: category,
+ defaultReporter(type, category, eventName, eventValue) {
+ const detail = {
+ type,
+ category,
name: eventName,
value: eventValue,
};
- document.dispatchEvent(new CustomEvent(type, {detail: detail}));
+ document.dispatchEvent(new CustomEvent(type, {detail}));
if (type === ERROR.TYPE) {
console.error(eventValue.error || eventName);
} else {
@@ -113,15 +113,15 @@
}
},
- cachingReporter: function(type, category, eventName, eventValue) {
+ cachingReporter(type, category, eventName, eventValue) {
if (type === ERROR.TYPE) {
console.error(eventValue.error || eventName);
}
if (Gerrit._arePluginsLoaded()) {
if (pending.length) {
- pending.splice(0).forEach(function(args) {
- this.reporter.apply(this, args);
- }, this);
+ for (const args of pending.splice(0)) {
+ this.reporter(...args);
+ }
}
this.reporter(type, category, eventName, eventValue);
} else {
@@ -132,8 +132,8 @@
/**
* User-perceived app start time, should be reported when the app is ready.
*/
- appStarted: function() {
- var startTime =
+ appStarted() {
+ const startTime =
new Date().getTime() - this.performanceTiming.navigationStart;
this.reporter(
TIMING.TYPE, TIMING.CATEGORY, TIMING.APP_STARTED, startTime);
@@ -142,22 +142,22 @@
/**
* Page load time, should be reported at any time after navigation.
*/
- pageLoaded: function() {
+ pageLoaded() {
if (this.performanceTiming.loadEventEnd === 0) {
console.error('pageLoaded should be called after window.onload');
this.async(this.pageLoaded, 100);
} else {
- var loadTime = this.performanceTiming.loadEventEnd -
+ const loadTime = this.performanceTiming.loadEventEnd -
this.performanceTiming.navigationStart;
this.reporter(
- TIMING.TYPE, TIMING.CATEGORY, TIMING.PAGE_LOADED, loadTime);
+ TIMING.TYPE, TIMING.CATEGORY, TIMING.PAGE_LOADED, loadTime);
}
},
- locationChanged: function() {
- var page = '';
- var pathname = this._getPathname();
- if (pathname.indexOf('/q/') === 0) {
+ locationChanged() {
+ let page = '';
+ const pathname = this._getPathname();
+ if (pathname.startsWith('/q/')) {
page = this.getBaseUrl() + '/q/';
} else if (pathname.match(CHANGE_VIEW_REGEX)) { // change view
page = this.getBaseUrl() + '/c/';
@@ -171,32 +171,32 @@
NAVIGATION.TYPE, NAVIGATION.CATEGORY, NAVIGATION.PAGE, page);
},
- pluginsLoaded: function() {
+ pluginsLoaded() {
this.timeEnd('PluginsLoaded');
},
- _getPathname: function() {
+ _getPathname() {
return '/' + window.location.pathname.substring(this.getBaseUrl().length);
},
/**
* Reset named timer.
*/
- time: function(name) {
+ time(name) {
this._baselines[name] = this.now();
},
/**
* Finish named timer and report it to server.
*/
- timeEnd: function(name) {
- var baseTime = this._baselines[name] || 0;
- var time = this.now() - baseTime;
+ timeEnd(name) {
+ const baseTime = this._baselines[name] || 0;
+ const time = this.now() - baseTime;
this.reporter(TIMING.TYPE, TIMING.CATEGORY, name, time);
delete this._baselines[name];
},
- reportInteraction: function(eventName, opt_msg) {
+ reportInteraction(eventName, opt_msg) {
this.reporter(INTERACTION_TYPE, this.category, eventName, opt_msg);
},
});
@@ -204,5 +204,4 @@
window.GrReporting = GrReporting;
// Expose onerror installation so it would be accessible from tests.
window.GrReporting._catchErrors = catchErrors;
-
})();
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 2720ebd..bda03b1 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -32,15 +32,15 @@
</test-fixture>
<script>
- suite('gr-reporting tests', function() {
- var element;
- var sandbox;
- var clock;
- var fakePerformance;
+ suite('gr-reporting tests', () => {
+ let element;
+ let sandbox;
+ let clock;
+ let fakePerformance;
- var NOW_TIME = 100;
+ const NOW_TIME = 100;
- setup(function() {
+ setup(() => {
sandbox = sinon.sandbox.create();
clock = sinon.useFakeTimers(NOW_TIME);
element = fixture('basic');
@@ -49,15 +49,15 @@
loadEventEnd: 2,
};
sinon.stub(element, 'performanceTiming',
- {get: function() {return fakePerformance;}});
+ {get() {return fakePerformance;}});
sandbox.stub(element, 'reporter');
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
clock.restore();
});
- test('appStarted', function() {
+ test('appStarted', () => {
element.appStarted();
assert.isTrue(
element.reporter.calledWithExactly(
@@ -66,7 +66,7 @@
));
});
- test('pageLoaded', function() {
+ test('pageLoaded', () => {
element.pageLoaded();
assert.isTrue(
element.reporter.calledWithExactly(
@@ -75,8 +75,8 @@
);
});
- test('time and timeEnd', function() {
- var nowStub = sandbox.stub(element, 'now').returns(0);
+ test('time and timeEnd', () => {
+ const nowStub = sandbox.stub(element, 'now').returns(0);
element.time('foo');
nowStub.returns(1);
element.time('bar');
@@ -92,14 +92,14 @@
));
});
- suite('plugins', function() {
- setup(function() {
+ suite('plugins', () => {
+ setup(() => {
element.reporter.restore();
sandbox.stub(element, 'defaultReporter');
sandbox.stub(Gerrit, '_arePluginsLoaded');
});
- test('pluginsLoaded reports time', function() {
+ test('pluginsLoaded reports time', () => {
Gerrit._arePluginsLoaded.returns(true);
sandbox.stub(element, 'now').returns(42);
element.pluginsLoaded();
@@ -108,19 +108,19 @@
));
});
- test('caches reports if plugins are not loaded', function() {
+ test('caches reports if plugins are not loaded', () => {
Gerrit._arePluginsLoaded.returns(false);
element.timeEnd('foo');
assert.isFalse(element.defaultReporter.called);
});
- test('reports if plugins are loaded', function() {
+ test('reports if plugins are loaded', () => {
Gerrit._arePluginsLoaded.returns(true);
element.timeEnd('foo');
assert.isTrue(element.defaultReporter.called);
});
- test('reports cached events preserving order', function() {
+ test('reports cached events preserving order', () => {
Gerrit._arePluginsLoaded.returns(false);
element.timeEnd('foo');
Gerrit._arePluginsLoaded.returns(true);
@@ -134,38 +134,38 @@
});
});
- suite('location changed', function() {
- var pathnameStub;
- setup(function() {
+ suite('location changed', () => {
+ let pathnameStub;
+ setup(() => {
pathnameStub = sinon.stub(element, '_getPathname');
});
- teardown(function() {
+ teardown(() => {
pathnameStub.restore();
});
- test('search', function() {
+ test('search', () => {
pathnameStub.returns('/q/foo');
element.locationChanged();
assert.isTrue(element.reporter.calledWithExactly(
'nav-report', 'Location Changed', 'Page', '/q/'));
});
- test('change view', function() {
+ test('change view', () => {
pathnameStub.returns('/c/42/');
element.locationChanged();
assert.isTrue(element.reporter.calledWithExactly(
'nav-report', 'Location Changed', 'Page', '/c/'));
});
- test('change view', function() {
+ test('change view', () => {
pathnameStub.returns('/c/41/2');
element.locationChanged();
assert.isTrue(element.reporter.calledWithExactly(
'nav-report', 'Location Changed', 'Page', '/c/'));
});
- test('diff view', function() {
+ test('diff view', () => {
pathnameStub.returns('/c/41/2/file.txt');
element.locationChanged();
assert.isTrue(element.reporter.calledWithExactly(
@@ -173,36 +173,35 @@
});
});
- suite('exception logging', function() {
- var fakeWindow;
- var reporter;
+ suite('exception logging', () => {
+ let fakeWindow;
+ let reporter;
- var emulateThrow = function(msg, url, line, column, error) {
+ const emulateThrow = function(msg, url, line, column, error) {
return fakeWindow.onerror(msg, url, line, column, error);
};
- setup(function() {
+ setup(() => {
reporter = sandbox.stub(GrReporting.prototype, 'reporter');
fakeWindow = {};
sandbox.stub(console, 'error');
window.GrReporting._catchErrors(fakeWindow);
});
- test('is reported', function() {
- var error = new Error('bar');
+ test('is reported', () => {
+ const error = new Error('bar');
emulateThrow('bar', 'http://url', 4, 2, error);
- assert.isTrue(
- reporter.calledWith('error', 'exception', 'bar'));
- var payload = reporter.lastCall.args[3];
+ assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
+ const payload = reporter.lastCall.args[3];
assert.deepEqual(payload, {
url: 'http://url',
line: 4,
column: 2,
- error: error,
+ error,
});
});
- test('prevent default event handler', function() {
+ test('prevent default event handler', () => {
assert.isTrue(emulateThrow());
});
});
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index d4e7ae2..4edf1be 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -82,6 +82,7 @@
];
var SELF_EXPRESSION = 'self';
+ var ME_EXPRESSION = 'me';
var MAX_AUTOCOMPLETE_RESULTS = 10;
@@ -176,9 +177,13 @@
}).then(function(accounts) {
// When the expression supplied is a beginning substring of 'self',
// add it as an autocomplete option.
- return SELF_EXPRESSION.indexOf(expression) === 0 ?
- accounts.concat([predicate + ':' + SELF_EXPRESSION]) :
- accounts;
+ if (SELF_EXPRESSION.indexOf(expression) === 0) {
+ return accounts.concat([predicate + ':' + SELF_EXPRESSION]);
+ } else if (ME_EXPRESSION.indexOf(expression) === 0) {
+ return accounts.concat([predicate + ':' + ME_EXPRESSION]);
+ } else {
+ return accounts;
+ }
});
},
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index 3ddc96b..05ceb34 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -160,6 +160,17 @@
});
});
+ test('Inserts me as option when valid', function(done) {
+ element._getSearchSuggestions('owner:m').then(function(s) {
+ assert.equal(s[0].value, 'owner:me');
+ }).then(function() {
+ element._getSearchSuggestions('owner:meme').then(function(s) {
+ assert.notEqual(s[0].value, 'owner:me');
+ done();
+ });
+ });
+ });
+
test('Autocompletes groups', function(done) {
element._getSearchSuggestions('ownerin:pol').then(function(s) {
assert.equal(s[0].value, 'ownerin:Polygerrit');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index 214454a..c407829 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -405,7 +405,6 @@
td.classList.add(line.type);
var html = this._escapeHTML(text);
html = this._addTabWrappers(html, this._prefs.tab_size);
-
if (!this._prefs.line_wrapping &&
this._textLength(text, this._prefs.tab_size) >
this._prefs.line_length) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 950a046..76d2a69 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -264,7 +264,8 @@
<option value="SIDE_BY_SIDE">Side By Side</option>
<option value="UNIFIED_DIFF">Unified</option>
</select>
- <span hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]">
+ <span id="diffPrefsContainer"
+ hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]" hidden>
<span class="preferences desktop">
<span
hidden$="[[_computeModeSelectHidden(_isImageDiff)]]">/</span>
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 33f11c5..f0f814f 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
@@ -15,6 +15,7 @@
'use strict';
var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+ var MERGE_LIST_PATH = '/MERGE_LIST';
var COMMENT_SAVE = 'Try again when all comments have saved.';
@@ -544,12 +545,16 @@
},
_computeFileDisplayName: function(path) {
- return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
+ if (path === COMMIT_MESSAGE_PATH) {
+ return 'Commit message';
+ } else if (path === MERGE_LIST_PATH) {
+ return 'Merge list';
+ }
+ return path;
},
_computeTruncatedFileDisplayName: function(path) {
- return path === COMMIT_MESSAGE_PATH ?
- 'Commit message' : util.truncatePath(path);
+ return util.truncatePath(this._computeFileDisplayName(path));
},
_computeFileSelected: function(path, currentPath) {
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 d54f715..663cd3b 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
@@ -252,6 +252,38 @@
'Should navigate to /c/42/1');
});
+ test('Diff preferences hidden when no prefs or logged out',
+ function() {
+ element._loggedIn = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = false;
+ element._prefs = {'font_size': '12'};
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element._loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.diffPrefsContainer.hidden);
+ });
+
+ test('prefsButton opens gr-diff-preferences', function() {
+ var handlePrefsTapSpy = sandbox.spy(element, '_handlePrefsTap');
+ var overlayOpenStub = sandbox.stub(element.$.diffPreferences,
+ 'open');
+ var prefsButton = Polymer.dom(element.root).querySelector('.prefsButton');
+
+ MockInteractions.tap(prefsButton);
+
+ assert.isTrue(handlePrefsTapSpy.called);
+ assert.isTrue(overlayOpenStub.called);
+ });
+
test('go up to change via kb without change loaded', function() {
element._changeNum = '42';
element._patchRange = {
@@ -327,6 +359,8 @@
'/foo/bar/baz');
assert.equal(element._computeFileDisplayName('/COMMIT_MSG'),
'Commit message');
+ assert.equal(element._computeFileDisplayName('/MERGE_LIST'),
+ 'Merge list');
});
test('jump to file dropdown with patch range', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index d560723..ba402f2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -72,6 +72,7 @@
type: Boolean,
reflectToAttribute: true,
},
+ noRenderOnPrefsChange: Boolean,
_loggedIn: {
type: Boolean,
value: false,
@@ -436,7 +437,7 @@
this.updateStyles();
- if (this._diff && this._comments) {
+ if (this._diff && this._comments && !this.noRenderOnPrefsChange) {
this._renderDiffTable();
}
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 811a7e9..f765b26 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -837,6 +837,59 @@
});
});
+ suite('change in preferences', function() {
+ setup(function() {
+ element._diff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
+ meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
+ lines: 560},
+ diff_header: [],
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ content: [{skip: 66}],
+ };
+ element._comments = {
+ meta: {
+ changeNum: '42',
+ patchRange: {
+ basePatchNum: 'PARENT',
+ patchNum: 3,
+ },
+ path: '/path/to/foo',
+ projectConfig: {foo: 'bar'},
+ },
+ left: [
+ {id: 'bc1', side: 'PARENT', __commentSide: 'left'},
+ {id: 'bc2', side: 'PARENT', __commentSide: 'left'},
+ {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'},
+ {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'},
+ ],
+ right: [
+ {id: 'c1', __commentSide: 'right'},
+ {id: 'c2', __commentSide: 'right'},
+ {id: 'd1', __draft: true, __commentSide: 'right'},
+ {id: 'd2', __draft: true, __commentSide: 'right'},
+ ],
+ };
+ });
+
+ test('change in preferences re-renders diff', function() {
+ sandbox.stub(element, '_renderDiffTable');
+ element.prefs = {};
+ element.prefs = {time_format: 'HHMM_12'};
+ assert.isTrue(element._renderDiffTable.called);
+ });
+
+ test('change in preferences does not re-renders diff with ' +
+ 'noRenderOnPrefsChange', function() {
+ sandbox.stub(element, '_renderDiffTable');
+ element.noRenderOnPrefsChange = true;
+ element.prefs = {};
+ element.prefs = {time_format: 'HHMM_12'};
+ assert.isFalse(element._renderDiffTable.called);
+ });
+ });
+
suite('handle comment-update', function() {
setup(function() {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 6f09701..87a4687 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -43,7 +43,7 @@
<style>
:host {
display: flex;
- min-height: 100vh;
+ min-height: 100%;
flex-direction: column;
}
gr-main-header,
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index 0e64cb2..91bc628 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -79,7 +79,7 @@
},
save: function() {
- if (!this.mutable || !this.hasUnsavedChanges) {
+ if (!this.hasUnsavedChanges) {
return Promise.resolve();
}
@@ -97,9 +97,9 @@
},
_maybeSetName: function() {
- return this._hasNameChange ?
- this.$.restAPI.setAccountName(this._account.name) :
- Promise.resolve();
+ return this._hasNameChange && this.mutable ?
+ this.$.restAPI.setAccountName(this._account.name) :
+ Promise.resolve();
},
_maybeSetStatus: function() {
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index 8f4e89d..cf35450 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -168,5 +168,92 @@
});
});
});
+
+ suite('edit name and status', function() {
+ var nameChangedSpy;
+ var statusChangedSpy;
+ var nameStub;
+ var statusStub;
+
+ setup(function() {
+ nameChangedSpy = sandbox.spy(element, '_nameChanged');
+ statusChangedSpy = sandbox.spy(element, '_statusChanged');
+ element.set('_serverConfig',
+ {auth: {editable_account_fields: ['FULL_NAME']}});
+
+ nameStub = sandbox.stub(element.$.restAPI, 'setAccountName',
+ function(name) { return Promise.resolve(); });
+ statusStub = sandbox.stub(element.$.restAPI, 'setAccountStatus',
+ function(status) { return Promise.resolve(); });
+ });
+
+ test('set name and status', function(done) {
+ assert.isTrue(element.mutable);
+ assert.isFalse(element.hasUnsavedChanges);
+
+ element.set('_account.name', 'new name');
+
+ assert.isTrue(nameChangedSpy.called);
+
+ element.set('_account.status', 'new status');
+
+ assert.isTrue(statusChangedSpy.called);
+
+ assert.isTrue(element.hasUnsavedChanges);
+
+ element.save().then(function() {
+ assert.isTrue(statusStub.called);
+ assert.isTrue(nameStub.called);
+
+ assert.equal(nameStub.lastCall.args[0], 'new name');
+
+ assert.equal(statusStub.lastCall.args[0], 'new status');
+
+ done();
+ });
+ });
+ });
+
+ suite('set status but read name', function() {
+ var statusChangedSpy;
+ var statusStub;
+
+ setup(function() {
+ statusChangedSpy = sandbox.spy(element, '_statusChanged');
+ element.set('_serverConfig',
+ {auth: {editable_account_fields: []}});
+
+ statusStub = sandbox.stub(element.$.restAPI, 'setAccountStatus',
+ function(status) { return Promise.resolve(); });
+ });
+
+ test('read full name but set status', function(done) {
+ var section = element.$.nameSection;
+ var displaySpan = section.querySelectorAll('.value')[0];
+ var inputSpan = section.querySelectorAll('.value')[1];
+
+ assert.isFalse(element.mutable);
+
+ assert.isFalse(element.hasUnsavedChanges);
+
+ assert.isFalse(displaySpan.hasAttribute('hidden'));
+ assert.equal(displaySpan.textContent, account.name);
+ assert.isTrue(inputSpan.hasAttribute('hidden'));
+
+ element.set('_account.status', 'new status');
+
+ assert.isTrue(statusChangedSpy.called);
+
+ assert.isTrue(element.hasUnsavedChanges);
+
+ element.save().then(function() {
+ assert.isTrue(statusStub.called);
+ statusStub.lastCall.returnValue.then(function() {
+ assert.equal(statusStub.lastCall.args[0], 'new status');
+ done();
+ });
+ });
+ });
+ });
});
</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 e3aea75..f485dd6 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
@@ -118,11 +118,10 @@
<fieldset id="profile">
<gr-account-info
id="accountInfo"
- mutable="{{_accountInfoMutable}}"
+ mutable="{{_accountNameMutable}}"
has-unsaved-changes="{{_accountInfoChanged}}"></gr-account-info>
<gr-button
on-tap="_handleSaveAccountInfo"
- hidden$="[[!_accountInfoMutable]]"
disabled="[[!_accountInfoChanged]]">Save changes</gr-button>
</fieldset>
<h2
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 4c852b5..4647a2d 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -48,7 +48,7 @@
type: Object,
value: function() { return {}; },
},
- _accountInfoMutable: Boolean,
+ _accountNameMutable: Boolean,
_accountInfoChanged: Boolean,
_diffPrefs: Object,
_changeTableColumnsNotDisplayed: Array,
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js
index 9d6b83b..aeb9607 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js
@@ -22,8 +22,8 @@
this._el.setLabelValue(label, value);
};
- GrChangeReplyInterface.prototype.send = function() {
- return this._el.send();
+ GrChangeReplyInterface.prototype.send = function(opt_includeComments) {
+ return this._el.send(opt_includeComments);
};
window.GrChangeReplyInterface = GrChangeReplyInterface;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
index d7d5cfe..c2357cc 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -66,8 +66,8 @@
assert(setLabelValueStub.calledWithExactly('My-Label', '+1337'));
var sendStub = sinon.stub(element, 'send');
- changeReply.send();
- assert(sendStub.calledWithExactly());
+ changeReply.send(false);
+ assert(sendStub.calledWithExactly(false));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 7b0ce19..ae42c2a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1102,5 +1102,24 @@
return response.ok;
});
},
+
+ startWorkInProgress: function(changeNum, opt_message) {
+ var payload = {};
+ if (opt_message) {
+ payload.message = opt_message;
+ }
+ var url = this.getChangeActionURL(changeNum, null, '/wip');
+ return this.send('POST', url, payload)
+ .then(function(response) {
+ if (response.status === 204) {
+ return 'Change marked as Work In Progress.';
+ }
+ });
+ },
+
+ startReview: function(changeNum, review) {
+ return this.send(
+ 'POST', this.getChangeActionURL(changeNum, null, '/ready'), review);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index f2f41b8..933b4f4 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -613,5 +613,22 @@
}
);
});
+
+ test('startWorkInProgress', function() {
+ sandbox.stub(element, 'send').returns(Promise.resolve('ok'));
+ element.startWorkInProgress('42');
+ assert.isTrue(element.send.calledWith(
+ 'POST', '/changes/42/wip', {}));
+ element.startWorkInProgress('42', 'revising...');
+ assert.isTrue(element.send.calledWith(
+ 'POST', '/changes/42/wip', {message: 'revising...'}));
+ });
+
+ test('startReview', function() {
+ sandbox.stub(element, 'send').returns(Promise.resolve({}));
+ element.startReview('42', {message: 'Please review.'});
+ assert.isTrue(element.send.calledWith(
+ 'POST', '/changes/42/ready', {message: 'Please review.'}));
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/test/plugin.html b/polygerrit-ui/app/elements/test/plugin.html
index 0314655..4efd6cf 100644
--- a/polygerrit-ui/app/elements/test/plugin.html
+++ b/polygerrit-ui/app/elements/test/plugin.html
@@ -11,6 +11,7 @@
html {
--primary-text-color: #F00BAA;
--header-background-color: #F01BAA;
+ --header-title-content: "MyGerrit";
--footer-background-color: #F02BAA;
}
</style>
diff --git a/polygerrit-ui/app/lint_test.sh b/polygerrit-ui/app/lint_test.sh
new file mode 100755
index 0000000..7ee74d8
--- /dev/null
+++ b/polygerrit-ui/app/lint_test.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -ex
+
+eslint_bin=$(which npm)
+if [[ -z "$eslint_bin" ]]; then
+ echo "NPM must be on the path."
+ exit 1
+fi
+
+eslint_bin=$(which eslint)
+eslint_config=$(npm list -g | grep -c eslint-config-google)
+eslint_plugin=$(npm list -g | grep -c eslint-plugin-html)
+if [[ -z "$eslint_bin" ]] || [[ eslint_config -eq "0" ]] || [[ eslint_plugin -eq "0" ]]; then
+ echo "You must install ESLint and its dependencies from NPM."
+ echo "> npm install -g eslint eslint-config-google eslint-plugin-html"
+ echo "For more information, view the README:"
+ echo "https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/#Style-guide"
+ exit 1
+fi
+
+${eslint_bin} --ignore-pattern 'bower_components/' --ignore-pattern 'gr-linked-text' --ignore-pattern 'scripts/vendor' --ext .html,.js .
diff --git a/polygerrit-ui/app/styles/app-theme.html b/polygerrit-ui/app/styles/app-theme.html
index e88b70c..c998f03 100644
--- a/polygerrit-ui/app/styles/app-theme.html
+++ b/polygerrit-ui/app/styles/app-theme.html
@@ -18,6 +18,9 @@
/* Following vars have LTS for plugin API. */
--primary-text-color: #000;
--header-background-color: #eee;
+ --header-title-content: 'PolyGerrit';
+ --header-icon: none;
+ --header-icon-size: 0em;
--footer-background-color: var(--header-background-color);
/* Following are not part of plugin API. */
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
index 2dbeae7..55bfae1 100644
--- a/tools/bzl/maven_jar.bzl
+++ b/tools/bzl/maven_jar.bzl
@@ -64,12 +64,13 @@
formatted_deps += " ],"
return formatted_deps
-def _generate_build_file(ctx, binjar, srcjar):
+def _generate_build_files(ctx, binjar, srcjar):
+ header = "# DO NOT EDIT: automatically generated BUILD file for maven_jar rule %s" % ctx.name
srcjar_attr = ""
if srcjar:
srcjar_attr = 'srcjar = "%s",' % srcjar
contents = """
-# DO NOT EDIT: automatically generated BUILD file for maven_jar rule {rule_name}
+{header}
package(default_visibility = ['//visibility:public'])
java_import(
name = 'jar',
@@ -86,10 +87,10 @@
{exports}
)
\n""".format(srcjar_attr = srcjar_attr,
- rule_name = ctx.name,
- binjar = binjar,
- deps = _format_deps("deps", ctx.attr.deps),
- exports = _format_deps("exports", ctx.attr.exports))
+ header = header,
+ binjar = binjar,
+ deps = _format_deps("deps", ctx.attr.deps),
+ exports = _format_deps("exports", ctx.attr.exports))
if srcjar:
contents += """
java_import(
@@ -99,6 +100,18 @@
""".format(srcjar = srcjar)
ctx.file('%s/BUILD' % ctx.path("jar"), contents, False)
+ # Compatibility layer for java_import_external from rules_closure
+ contents = """
+{header}
+package(default_visibility = ['//visibility:public'])
+
+alias(
+ name = "{rule_name}",
+ actual = "@{rule_name}//jar",
+)
+\n""".format(rule_name = ctx.name, header = header)
+ ctx.file("BUILD", contents, False)
+
def _maven_jar_impl(ctx):
"""rule to download a Maven archive."""
coordinates = _create_coordinates(ctx.attr.artifact)
@@ -142,7 +155,7 @@
if out.return_code:
fail("failed %s: %s" % (args, out.stderr))
- _generate_build_file(ctx, binjar, srcjar)
+ _generate_build_files(ctx, binjar, srcjar)
maven_jar = repository_rule(
attrs = {
diff --git a/tools/coverage.sh b/tools/coverage.sh
new file mode 100755
index 0000000..8fa979f
--- /dev/null
+++ b/tools/coverage.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Usage
+#
+# COVERAGE_CPUS=32 tools/coverage.sh [/path/to/report-directory/]
+#
+# COVERAGE_CPUS defaults to 2, and the default destination is a temp
+# dir.
+
+genhtml=$(which genhtml)
+if [[ -z "${genhtml}" ]]; then
+ echo "Install 'genhtml' (contained in the 'lcov' package)"
+ exit 1
+fi
+
+destdir="$1"
+if [[ -z "${destdir}" ]]; then
+ destdir=$(mktemp -d /tmp/gerritcov.XXXXXX)
+fi
+
+echo "Running 'bazel coverage'; this may take a while"
+
+# coverage is expensive to run; use --jobs=2 to avoid overloading the
+# machine.
+bazel coverage -k --jobs=${COVERAGE_CPUS:-2} -- ... -//gerrit-common:auto_value_tests
+
+# The coverage data contains filenames relative to the Java root, and
+# genhtml has no logic to search these elsewhere. Workaround this
+# limitation by running genhtml in a directory with the files in the
+# right place. Also -inexplicably- genhtml wants to have the source
+# files relative to the output directory.
+mkdir -p ${destdir}/
+cp -a */src/{main,test}/java/* ${destdir}/
+
+base=$(bazel info bazel-testlogs)
+for f in $(find ${base} -name 'coverage.dat') ; do
+ cp $f ${destdir}/$(echo $f| sed "s|${base}/||" | sed "s|/|_|g")
+done
+
+cd ${destdir}
+find -name '*coverage.dat' -size 0 -delete
+
+genhtml -o . --ignore-errors source *coverage.dat
+
+echo "coverage report at file://${destdir}/index.html"