Merge "Wiki-like formatting for PolyGerrit comments"
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 1f9dd33..5a82d5a 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -230,6 +230,19 @@
Allowed range of values are 0 (Patch Set Unlocked) to 1 (Patch Set
Locked).
+[[label_allowPostSubmit]]
+=== `label.Label-Name.allowPostSubmit`
+
+If true, the label may be voted on for changes that have already been
+submitted. If false, the label will not appear in the UI and will not
+be accepted when reviewing a closed change.
+
+In either case, voting on a label after submission is only permitted if
+the new vote is at least as high as the old vote by that user. This
+avoids creating the false impression that a post-submit vote can change
+the past and affect submission somehow.
+
+Defaults to true.
[[label_copyMinScore]]
=== `label.Label-Name.copyMinScore`
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index 08d0a54..0f73bd3 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -25,12 +25,13 @@
database as backend so no set up or configuration is necessary.
Currently only support for embedded mode is added. There are two other
-deployment options for Apache Derby that can be added later [1]:
-+
-* Derby Network Server (standalone mode)
-* Embedded Server (hybrid mode)
-+
-[1] http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#ns
+deployment options for Apache Derby that can be added later:
+
+* link:http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#Network+Server+Options[
+Derby Network Server (standalone mode)]
+
+* link:http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#Embedded+Server[
+Embedded Server (hybrid mode)]
[[createdb_postgres]]
=== PostgreSQL
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 11b2729..3137e16 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2792,6 +2792,64 @@
Adding query parameter `links` (for example `/changes/.../commit?links`)
returns a link:#commit-info[CommitInfo] with the additional field `web_links`.
+[[get-description]]
+=== Get Description
+--
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/description'
+--
+
+Retrieves the description of a patch set.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/description HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ "Added Documentation"
+----
+
+If the patch set does not have a description an empty string is returned.
+
+[[set-description]]
+=== Set Description
+--
+'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/description'
+--
+
+Sets the description of a patch set.
+
+The new description must be provided in the request body inside a
+link:#description-input[DescriptionInput] entity.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/description HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "description": "Added Documentation"
+ }
+----
+
+As response the new description is returned.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ "Added Documentation"
+----
+
[[get-merge-list]]
=== Get Merge List
--
@@ -5097,6 +5155,16 @@
If not set, the default is `ALL`.
|=======================
+[[description-input]]
+=== DescriptionInput
+The `DescriptionInput` entity contains information for setting a description.
+
+[options="header",cols="1,6"]
+|===========================
+|Field Name |Description
+|`description` |The description text.
+|===========================
+
[[diff-content]]
=== DiffContent
The `DiffContent` entity contains information about the content differences
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 1910049..9cc687b 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -20,6 +20,7 @@
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.HEAD;
import com.google.common.base.Strings;
@@ -30,6 +31,7 @@
import com.google.common.collect.Sets;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GroupReference;
@@ -138,6 +140,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
@@ -1138,4 +1141,27 @@
assertThat(contentEntry.editB).isNull();
assertThat(contentEntry.skip).isNull();
}
+
+ protected TestRepository<?> createProjectWithPush(String name,
+ @Nullable Project.NameKey parent,
+ SubmitType submitType) throws Exception {
+ Project.NameKey project = createProject(name, parent, true, submitType);
+ grant(Permission.PUSH, project, "refs/heads/*");
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
+ return cloneProject(project);
+ }
+
+ protected void assertPermitted(ChangeInfo info, String label,
+ Integer... expected) {
+ assertThat(info.permittedLabels).isNotNull();
+ Collection<String> strs = info.permittedLabels.get(label);
+ if (expected.length == 0) {
+ assertThat(strs).isNull();
+ } else {
+ assertThat(
+ strs.stream().map(s -> Integer.valueOf(s.trim()))
+ .collect(toList()))
+ .containsExactlyElementsIn(Arrays.asList(expected));
+ }
+ }
}
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 234d2fa..e7557fd 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
@@ -2304,6 +2304,8 @@
.containsExactly("Code-Review", "Verified");
assertThat(change.permittedLabels.keySet())
.containsExactly("Code-Review", "Verified");
+ assertPermitted(change, "Code-Review", -2, -1, 0, 1, 2);
+ assertPermitted(change, "Verified", -1, 0, 1);
// add an approval on the new label
gApi.changes()
@@ -2344,6 +2346,7 @@
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
assertThat(change.labels.keySet()).containsExactly("Code-Review");
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 2);
// add new label and assert that it's returned for existing changes
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
@@ -2363,6 +2366,8 @@
.containsExactly("Code-Review", "Verified");
assertThat(change.permittedLabels.keySet())
.containsExactly("Code-Review", "Verified");
+ assertPermitted(change, "Code-Review", 2);
+ assertPermitted(change, "Verified", 0, 1);
// ignore the new label by Prolog submit rule and assert that the label is
// no longer returned
@@ -2378,8 +2383,8 @@
change = gApi.changes()
.id(r.getChangeId())
.get();
- assertThat(change.labels.keySet()).containsExactly("Code-Review");
- assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 2);
+ assertPermitted(change, "Verified");
// add an approval on the new label and assert that the label is now
// returned although it is ignored by the Prolog submit rule and hence not
@@ -2395,7 +2400,8 @@
.get();
assertThat(change.labels.keySet())
.containsExactly("Code-Review", "Verified");
- assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 2);
+ assertPermitted(change, "Verified");
// remove label and assert that it's no longer returned for existing
// changes, even if there is an approval for it
@@ -2410,6 +2416,7 @@
.get();
assertThat(change.labels.keySet()).containsExactly("Code-Review");
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 2);
}
@Test
@@ -2425,7 +2432,7 @@
.get();
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
assertThat(change.labels.keySet()).containsExactly("Code-Review");
- assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 0, 1, 2);
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 2925c1d..ab4daf2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -20,6 +20,7 @@
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -44,7 +45,6 @@
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -87,6 +87,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
public class RevisionIT extends AbstractDaemonTest {
@@ -166,6 +167,9 @@
approval = getApproval(changeId, label);
assertThat(approval.value).isEqualTo(1);
assertThat(approval.postSubmit).isNull();
+ assertPermitted(
+ gApi.changes().id(changeId).get(EnumSet.of(DETAILED_LABELS)),
+ "Code-Review", 1, 2);
// Repeating the current label is allowed. Does not flip the postSubmit bit
// due to deduplication codepath.
@@ -200,6 +204,9 @@
approval = getApproval(changeId, label);
assertThat(approval.value).isEqualTo(2);
assertThat(approval.postSubmit).isTrue();
+ assertPermitted(
+ gApi.changes().id(changeId).get(EnumSet.of(DETAILED_LABELS)),
+ "Code-Review", 2);
// Decreasing to previous post-submit vote is still not allowed.
try {
@@ -217,6 +224,53 @@
assertThat(approval.postSubmit).isTrue();
}
+ @Test
+ public void postSubmitApprovalAfterVoteRemoved() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+
+ setApiUser(admin);
+ revision(r).review(ReviewInput.approve());
+
+ setApiUser(user);
+ revision(r).review(ReviewInput.recommend());
+
+ setApiUser(admin);
+ gApi.changes()
+ .id(changeId)
+ .reviewer(user.username)
+ .deleteVote("Code-Review");
+ Optional<ApprovalInfo> crUser = get(changeId, DETAILED_LABELS)
+ .labels.get("Code-Review").all.stream()
+ .filter(a -> a._accountId == user.id.get()).findFirst();
+ assertThat(crUser.isPresent()).isTrue();
+ assertThat(crUser.get().value).isEqualTo(0);
+
+ revision(r).submit();
+
+ setApiUser(user);
+ ReviewInput in = new ReviewInput();
+ in.label("Code-Review", 0);
+ in.message = "Still LGTM";
+ revision(r).review(in);
+ }
+
+ @Test
+ public void postSubmitDeleteApprovalNotAllowed() throws Exception {
+ PushOneCommit.Result r = createChange();
+
+ revision(r).review(ReviewInput.approve());
+ revision(r).submit();
+
+ ReviewInput in = new ReviewInput();
+ in.label("Code-Review", 0);
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "Cannot reduce vote on labels for closed change: Code-Review");
+ revision(r).review(in);
+ }
+
@TestProjectInput(submitType = SubmitType.CHERRY_PICK)
@Test
public void approvalCopiedDuringSubmitIsNotPostSubmit() throws Exception {
@@ -771,6 +825,31 @@
}
@Test
+ public void description() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description()).isEqualTo("");
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description("test");
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description()).isEqualTo("test");
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description("");
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description()).isEqualTo("");
+ }
+
+ @Test
public void content() throws Exception {
PushOneCommit.Result r = createChange();
assertContent(r, FILE_NAME, FILE_CONTENT);
@@ -946,11 +1025,11 @@
public void actions() throws Exception {
PushOneCommit.Result r = createChange();
assertThat(current(r).actions().keySet())
- .containsExactly("cherrypick", "rebase");
+ .containsExactly("cherrypick", "description", "rebase");
current(r).review(ReviewInput.approve());
assertThat(current(r).actions().keySet())
- .containsExactly("submit", "cherrypick", "rebase");
+ .containsExactly("submit", "cherrypick", "description", "rebase");
current(r).submit();
assertThat(current(r).actions().keySet())
@@ -1090,7 +1169,7 @@
throws Exception {
ChangeInfo info = gApi.changes()
.id(changeId)
- .get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
+ .get(EnumSet.of(DETAILED_LABELS));
LabelInfo li = info.labels.get(label);
assertThat(li).isNotNull();
int accountId = atrScope.get().getUser().getAccountId().get();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 9927c15..cfdc226 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -26,6 +26,7 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.fail;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -34,6 +35,7 @@
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.InheritableBoolean;
@@ -81,6 +83,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
@NoHttpd
public abstract class AbstractSubmit extends AbstractDaemonTest {
@@ -347,26 +350,126 @@
}
@Test
+ public void submitWholeTopicMultipleProjects() throws Exception {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ String topic = "test-topic";
+
+ // Create test projects
+ TestRepository<?> repoA = createProjectWithPush(
+ "project-a", null, getSubmitType());
+ TestRepository<?> repoB = createProjectWithPush(
+ "project-b", null, getSubmitType());
+
+ // Create changes on project-a
+ PushOneCommit.Result change1 =
+ createChange(repoA, "master", "Change 1", "a.txt", "content", topic);
+ PushOneCommit.Result change2 =
+ createChange(repoA, "master", "Change 2", "b.txt", "content", topic);
+
+ // Create changes on project-b
+ PushOneCommit.Result change3 =
+ createChange(repoB, "master", "Change 3", "a.txt", "content", topic);
+ PushOneCommit.Result change4 =
+ createChange(repoB, "master", "Change 4", "b.txt", "content", topic);
+
+ approve(change1.getChangeId());
+ approve(change2.getChangeId());
+ approve(change3.getChangeId());
+ approve(change4.getChangeId());
+ submit(change4.getChangeId());
+
+ String expectedTopic = name(topic);
+ change1.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change2.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change3.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change4.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ }
+
+ @Test
+ public void submitWholeTopicMultipleBranchesOnSameProject() throws Exception {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ String topic = "test-topic";
+
+ // Create test project
+ String projectName = "project-a";
+ TestRepository<?> repoA = createProjectWithPush(
+ projectName, null, getSubmitType());
+
+ RevCommit initialHead =
+ getRemoteHead(new Project.NameKey(name(projectName)), "master");
+
+ // Create the dev branch on the test project
+ BranchInput in = new BranchInput();
+ in.revision = initialHead.name();
+ gApi.projects().name(name(projectName)).branch("dev").create(in);
+
+ // Create changes on master
+ PushOneCommit.Result change1 =
+ createChange(repoA, "master", "Change 1", "a.txt", "content", topic);
+ PushOneCommit.Result change2 =
+ createChange(repoA, "master", "Change 2", "b.txt", "content", topic);
+
+ // Create changes on dev
+ repoA.reset(initialHead);
+ PushOneCommit.Result change3 =
+ createChange(repoA, "dev", "Change 3", "a.txt", "content", topic);
+ PushOneCommit.Result change4 =
+ createChange(repoA, "dev", "Change 4", "b.txt", "content", topic);
+
+ approve(change1.getChangeId());
+ approve(change2.getChangeId());
+ approve(change3.getChangeId());
+ approve(change4.getChangeId());
+ submit(change4.getChangeId());
+
+ String expectedTopic = name(topic);
+ change1.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change2.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change3.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change4.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ }
+
+ @Test
public void submitWholeTopic() throws Exception {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ String topic = "test-topic";
PushOneCommit.Result change1 =
- createChange("Change 1", "a.txt", "content", "test-topic");
+ createChange("Change 1", "a.txt", "content", topic);
PushOneCommit.Result change2 =
- createChange("Change 2", "b.txt", "content", "test-topic");
+ createChange("Change 2", "b.txt", "content", topic);
PushOneCommit.Result change3 =
- createChange("Change 3", "c.txt", "content", "test-topic");
+ createChange("Change 3", "c.txt", "content", topic);
approve(change1.getChangeId());
approve(change2.getChangeId());
approve(change3.getChangeId());
submit(change3.getChangeId());
- change1.assertChange(Change.Status.MERGED, name("test-topic"), admin);
- change2.assertChange(Change.Status.MERGED, name("test-topic"), admin);
- change3.assertChange(Change.Status.MERGED, name("test-topic"), admin);
+ String expectedTopic = name(topic);
+ change1.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change2.assertChange(Change.Status.MERGED, expectedTopic, admin);
+ change3.assertChange(Change.Status.MERGED, expectedTopic, admin);
+
// Check for the exact change to have the correct submitter.
assertSubmitter(change3);
// Also check submitters for changes submitted via the topic relationship.
assertSubmitter(change1);
assertSubmitter(change2);
+
+ // Check that the repo has the expected commits
+ List<RevCommit> log = getRemoteLog();
+ List<String> commitsInRepo = log.stream()
+ .map(c -> c.getShortMessage())
+ .collect(Collectors.toList());
+ int expectedCommitCount = getSubmitType() == SubmitType.MERGE_ALWAYS
+ ? 5 // initial commit + 3 commits + merge commit
+ : 4; // initial commit + 3 commits
+ assertThat(log).hasSize(expectedCommitCount);
+
+ assertThat(commitsInRepo).containsAllOf(
+ "Initial empty repository", "Change 1", "Change 2", "Change 3");
+ if (getSubmitType() == SubmitType.MERGE_ALWAYS) {
+ assertThat(commitsInRepo).contains(
+ "Merge changes from topic '" + expectedTopic + "'");
+ }
}
@Test
@@ -407,6 +510,66 @@
"A change to be submitted with " + num + " is not visible");
}
+ @Test
+ public void submitChangeWhenParentOfOtherBranchTip() throws Exception {
+ // Chain of two commits
+ // Push both to topic-branch
+ // Push the first commit for review and submit
+ //
+ // C2 -- tip of topic branch
+ // |
+ // C1 -- pushed for review
+ // |
+ // C0 -- Master
+ //
+ ProjectConfig config = projectCache.checkedGet(project).getConfig();
+ config.getProject().setCreateNewChangeForAllNotInTarget(
+ InheritableBoolean.TRUE);
+ saveProjectConfig(project, config);
+
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent(), testRepo,
+ PushOneCommit.SUBJECT, "a.txt", "content");
+ PushOneCommit.Result c1 = push1.to("refs/heads/topic");
+ c1.assertOkStatus();
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent(), testRepo,
+ PushOneCommit.SUBJECT, "b.txt", "anotherContent");
+ PushOneCommit.Result c2 = push2.to("refs/heads/topic");
+ c2.assertOkStatus();
+
+ PushOneCommit.Result change1 = push1.to("refs/for/master");
+ change1.assertOkStatus();
+
+ approve(change1.getChangeId());
+ submit(change1.getChangeId());
+ }
+
+ @Test
+ public void submitMergeOfNonChangeBranchTip() throws Exception {
+ // Merge a branch with commits that have not been submitted as
+ // changes.
+ //
+ // M -- mergeCommit (pushed for review and submitted)
+ // | \
+ // | S -- stable (pushed directly to refs/heads/stable)
+ // | /
+ // I -- master
+ //
+ RevCommit master = getRemoteHead(project, "master");
+ PushOneCommit stableTip = pushFactory.create(db, admin.getIdent(), testRepo,
+ "Tip of branch stable", "stable.txt", "");
+ PushOneCommit.Result stable = stableTip.to("refs/heads/stable");
+ PushOneCommit mergeCommit = pushFactory.create(db, admin.getIdent(),
+ testRepo, "The merge commit", "merge.txt", "");
+ mergeCommit.setParents(ImmutableList.of(master, stable.getCommit()));
+ PushOneCommit.Result mergeReview = mergeCommit.to("refs/for/master");
+ approve(mergeReview.getChangeId());
+ submit(mergeReview.getChangeId());
+
+ List<RevCommit> log = getRemoteLog();
+ assertThat(log).contains(stable.getCommit());
+ assertThat(log).contains(mergeReview.getCommit());
+ }
+
private void assertSubmitter(PushOneCommit.Result change) throws Exception {
ChangeInfo info = get(change.getChangeId(), ListChangesOption.MESSAGES);
assertThat(info.messages).isNotNull();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 4bbc159..98a94b1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -84,9 +84,10 @@
public void revisionActionsOneChangePerTopicUnapproved() throws Exception {
String changeId = createChangeWithTopic().getChangeId();
Map<String, ActionInfo> actions = getActions(changeId);
+ assertThat(actions).hasSize(3);
assertThat(actions).containsKey("cherrypick");
assertThat(actions).containsKey("rebase");
- assertThat(actions).hasSize(2);
+ assertThat(actions).containsKey("description");
}
@Test
@@ -436,9 +437,10 @@
}
private void commonActionsAssertions(Map<String, ActionInfo> actions) {
- assertThat(actions).hasSize(3);
+ assertThat(actions).hasSize(4);
assertThat(actions).containsKey("cherrypick");
assertThat(actions).containsKey("submit");
+ assertThat(actions).containsKey("description");
assertThat(actions).containsKey("rebase");
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
index d724653..7d59fb1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.server.mail;
import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -24,7 +25,10 @@
import com.google.gerrit.server.mail.MailUtil;
import com.google.gerrit.server.mail.send.EmailHeader;
import com.google.gerrit.testutil.FakeEmailSender;
+import com.google.gerrit.testutil.TestTimeUtil;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import java.sql.Timestamp;
@@ -38,6 +42,20 @@
/** Tests the presence of required metadata in email headers, text and html. */
public class MailMetadataIT extends AbstractDaemonTest {
+ private String systemTimeZone;
+
+ @Before
+ public void setTimeForTesting() {
+ systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+ TestTimeUtil.resetWithClockStep(1, SECONDS);
+ }
+
+ @After
+ public void resetTime() {
+ TestTimeUtil.useSystemTime();
+ System.setProperty("user.timezone", systemTimeZone);
+ }
+
@Test
public void metadataOnNewChange() throws Exception {
PushOneCommit.Result newChange = createChange();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 6f4cc45..8b1690f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -26,11 +26,13 @@
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.events.CommentAddedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
@@ -175,6 +177,34 @@
assertThat(q.blocking).isTrue();
}
+ @Test
+ public void customLabel_DisallowPostSubmit() throws Exception {
+ label.setFunctionName("NoOp");
+ label.setAllowPostSubmit(false);
+ P.setFunctionName("NoOp");
+ saveLabelConfig();
+
+ PushOneCommit.Result r = createChange();
+ revision(r).review(ReviewInput.approve());
+ revision(r).submit();
+
+ ChangeInfo info = get(r.getChangeId(), ListChangesOption.DETAILED_LABELS);
+ assertPermitted(info, "Code-Review", 2);
+ assertPermitted(info, P.getName(), 0, 1);
+ assertPermitted(info, label.getName());
+
+ ReviewInput in = new ReviewInput();
+ in.label(P.getName(), P.getMax().getValue());
+ revision(r).review(in);
+
+ in = new ReviewInput();
+ in.label(label.getName(), label.getMax().getValue());
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "Voting on labels disallowed after submit: " + label.getName());
+ revision(r).review(in);
+ }
+
private void saveLabelConfig() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
cfg.getLabelSections().put(label.getName(), label);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index c999c9c..e6efc0a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -19,6 +19,8 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.Sandboxed;
+import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
@@ -37,6 +39,7 @@
import java.util.List;
@NoHttpd
+@Sandboxed
public class ProjectWatchIT extends AbstractDaemonTest {
@Test
public void newPatchSetsNotifyConfig() throws Exception {
@@ -111,12 +114,18 @@
@Test
public void watchFile() throws Exception {
- // watch file in project
String watchedProject = createProject("watchedProject").get();
+ String otherWatchedProject = createProject("otherWatchedProject").get();
setApiUser(user);
+
+ // watch file in project as user
watch(watchedProject, "file:a.txt");
- // push a change to watched file -> should trigger email notification
+ // watch other project as user
+ watch(otherWatchedProject, null);
+
+ // push a change to watched file -> should trigger email notification for
+ // user
setApiUser(admin);
TestRepository<InMemoryRepository> watchedRepo =
cloneProject(new Project.NameKey(watchedProject), admin);
@@ -125,10 +134,87 @@
.to("refs/for/master");
r.assertOkStatus();
+ // assert email notification for user
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body()).contains("Change subject: TRIGGER\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ sender.clear();
+
+ // watch project as user2
+ TestAccount user2 = accounts.create("user2", "user2@test.com", "User2");
+ setApiUser(user2);
+ watch(watchedProject, null);
+
// push a change to non-watched file -> should not trigger email
- // notification
- r = pushFactory.create(db, admin.getIdent(), testRepo,
- "DONT_TRIGGER", "b.txt", "b1").to("refs/for/master");
+ // notification for user, only for user2
+ r = pushFactory.create(db, admin.getIdent(), watchedRepo,
+ "TRIGGER_USER2", "b.txt", "b1").to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification
+ messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user2.emailAddress);
+ assertThat(m.body()).contains("Change subject: TRIGGER_USER2\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ }
+
+ @Test
+ public void watchKeyword() throws Exception {
+ String watchedProject = createProject("watchedProject").get();
+ setApiUser(user);
+
+ // watch keyword in project as user
+ watch(watchedProject, "multimaster");
+
+ // push a change with keyword -> should trigger email notification
+ setApiUser(admin);
+ TestRepository<InMemoryRepository> watchedRepo =
+ cloneProject(new Project.NameKey(watchedProject), admin);
+ PushOneCommit.Result r = pushFactory
+ .create(db, admin.getIdent(), watchedRepo,
+ "Document multimaster setup", "a.txt", "a1")
+ .to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification for user
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body())
+ .contains("Change subject: Document multimaster setup\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ sender.clear();
+
+ // push a change without keyword -> should not trigger email notification
+ r = pushFactory.create(db, admin.getIdent(), watchedRepo,
+ "Cleanup cache implementation", "b.txt", "b1").to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification
+ assertThat(sender.getMessages()).hasSize(0);
+ }
+
+ @Test
+ public void watchAllProjects() throws Exception {
+ String anyProject = createProject("anyProject").get();
+ setApiUser(user);
+
+ // watch the All-Projects project to watch all projects
+ watch(allProjects.get(), null);
+
+ // push a change to any project -> should trigger email notification
+ setApiUser(admin);
+ TestRepository<InMemoryRepository> anyRepo =
+ cloneProject(new Project.NameKey(anyProject), admin);
+ PushOneCommit.Result r = pushFactory
+ .create(db, admin.getIdent(), anyRepo, "TRIGGER", "a", "a1")
+ .to("refs/for/master");
r.assertOkStatus();
// assert email notification
@@ -140,6 +226,93 @@
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
}
+ @Test
+ public void watchFileAllProjects() throws Exception {
+ String anyProject = createProject("anyProject").get();
+ setApiUser(user);
+
+ // watch file in All-Projects project as user to watch the file in all
+ // projects
+ watch(allProjects.get(), "file:a.txt");
+
+ // push a change to watched file in any project -> should trigger email
+ // notification for user
+ setApiUser(admin);
+ TestRepository<InMemoryRepository> anyRepo =
+ cloneProject(new Project.NameKey(anyProject), admin);
+ PushOneCommit.Result r = pushFactory
+ .create(db, admin.getIdent(), anyRepo, "TRIGGER", "a.txt", "a1")
+ .to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification for user
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body()).contains("Change subject: TRIGGER\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ sender.clear();
+
+ // watch project as user2
+ TestAccount user2 = accounts.create("user2", "user2@test.com", "User2");
+ setApiUser(user2);
+ watch(anyProject, null);
+
+ // push a change to non-watched file in any project -> should not trigger
+ // email notification for user, only for user2
+ r = pushFactory.create(db, admin.getIdent(), anyRepo,
+ "TRIGGER_USER2", "b.txt", "b1").to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification
+ messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user2.emailAddress);
+ assertThat(m.body()).contains("Change subject: TRIGGER_USER2\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ }
+
+ @Test
+ public void watchKeywordAllProjects() throws Exception {
+ String anyProject = createProject("anyProject").get();
+ setApiUser(user);
+
+ // watch keyword in project as user
+ watch(allProjects.get(), "multimaster");
+
+ // push a change with keyword to any project -> should trigger email
+ // notification
+ setApiUser(admin);
+ TestRepository<InMemoryRepository> anyRepo =
+ cloneProject(new Project.NameKey(anyProject), admin);
+ PushOneCommit.Result r = pushFactory
+ .create(db, admin.getIdent(), anyRepo,
+ "Document multimaster setup", "a.txt", "a1")
+ .to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification for user
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body())
+ .contains("Change subject: Document multimaster setup\n");
+ assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
+ sender.clear();
+
+ // push a change without keyword to any project -> should not trigger email
+ // notification
+ r = pushFactory.create(db, admin.getIdent(), anyRepo,
+ "Cleanup cache implementation", "b.txt", "b1").to("refs/for/master");
+ r.assertOkStatus();
+
+ // assert email notification
+ assertThat(sender.getMessages()).hasSize(0);
+ }
+
private void watch(String project, String filter)
throws RestApiException {
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index b1e1243..8135c91 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -25,6 +25,7 @@
import java.util.Map;
public class LabelType {
+ public static final boolean DEF_ALLOW_POST_SUBMIT = true;
public static final boolean DEF_CAN_OVERRIDE = true;
public static final boolean DEF_COPY_ALL_SCORES_IF_NO_CHANGE = true;
public static final boolean DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE = false;
@@ -104,6 +105,7 @@
protected boolean copyAllScoresOnTrivialRebase;
protected boolean copyAllScoresIfNoCodeChange;
protected boolean copyAllScoresIfNoChange;
+ protected boolean allowPostSubmit;
protected short defaultValue;
protected List<LabelValue> values;
@@ -144,6 +146,7 @@
DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
setCopyMaxScore(DEF_COPY_MAX_SCORE);
setCopyMinScore(DEF_COPY_MIN_SCORE);
+ setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT);
}
public String getName() {
@@ -174,6 +177,14 @@
this.canOverride = canOverride;
}
+ public boolean allowPostSubmit() {
+ return allowPostSubmit;
+ }
+
+ public void setAllowPostSubmit(boolean allowPostSubmit) {
+ this.allowPostSubmit = allowPostSubmit;
+ }
+
public void setRefPatterns(List<String> refPatterns) {
this.refPatterns = refPatterns;
}
diff --git a/gerrit-elasticsearch/BUCK b/gerrit-elasticsearch/BUCK
index 83f373f..f3ccd01 100644
--- a/gerrit-elasticsearch/BUCK
+++ b/gerrit-elasticsearch/BUCK
@@ -35,6 +35,7 @@
deps = [
':elasticsearch',
'//gerrit-extension-api:api',
+ '//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-server:testutil',
'//gerrit-server:query_tests',
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
index 78590a1..9a6755f 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
@@ -27,12 +27,14 @@
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.common.settings.Settings;
@@ -41,6 +43,7 @@
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Test;
import java.io.File;
import java.nio.file.Path;
@@ -179,4 +182,13 @@
return httpAddress.substring(httpAddress.indexOf(':') + 1,
httpAddress.length());
}
+
+ @Test
+ public void byOwnerInvalidQuery() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ insert(repo, newChange(repo), userId);
+ String nameEmail = user.asIdentifiedUser().getNameEmail();
+ assertQuery("owner: \"" + nameEmail + "\"\\");
+ }
+
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 8ada55a..2375b7e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -33,6 +33,9 @@
public interface RevisionApi {
void delete() throws RestApiException;
+ String description() throws RestApiException;
+ void description(String description) throws RestApiException;
+
void review(ReviewInput in) throws RestApiException;
void submit() throws RestApiException;
@@ -283,5 +286,15 @@
public MergeListRequest getMergeList() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public void description(String description) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public String description() throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 9ec1356..8f00a45 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -56,7 +56,6 @@
String collapsed();
String expanded();
String clippy();
- String parentWebLink();
}
@UiField Style style;
@@ -67,7 +66,6 @@
@UiField FlowPanel webLinkPanel;
@UiField TableRowElement firstParent;
@UiField FlowPanel parentCommits;
- @UiField FlowPanel parentWebLinks;
@UiField InlineHyperlink authorNameEmail;
@UiField Element authorDate;
@UiField InlineHyperlink committerNameEmail;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
index 5f476be..19f115d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -88,13 +88,6 @@
margin-left:2px;
}
- .parentWebLink a:first-child {
- margin-left:16px;
- }
- .parentWebLink>a {
- margin-left:2px;
- }
-
.commit {
margin-right: 3px;
float: left;
@@ -185,9 +178,6 @@
<td>
<g:FlowPanel ui:field='parentCommits'/>
</td>
- <td>
- <g:FlowPanel ui:field='parentWebLinks' styleName='{style.parentWebLink}'/>
- </td>
</tr>
<tr>
<th><ui:msg>Change-Id</ui:msg></th>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 83e238f..55c7e21 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -64,6 +64,8 @@
"autogenerated:gerrit:revert";
public static final String TAG_SET_ASSIGNEE =
"autogenerated:gerrit:setAssignee";
+ public static final String TAG_SET_DESCRIPTION =
+ "autogenerated:gerrit:setPsDescription";
public static final String TAG_SET_HASHTAGS =
"autogenerated:gerrit:setHashtag";
public static final String TAG_SET_TOPIC =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 5864a39..c5fb188 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -47,6 +47,7 @@
import com.google.gerrit.server.change.DraftComments;
import com.google.gerrit.server.change.FileResource;
import com.google.gerrit.server.change.Files;
+import com.google.gerrit.server.change.GetDescription;
import com.google.gerrit.server.change.GetMergeList;
import com.google.gerrit.server.change.GetPatch;
import com.google.gerrit.server.change.GetRevisionActions;
@@ -57,6 +58,7 @@
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.PreviewSubmit;
import com.google.gerrit.server.change.PublishDraftPatchSet;
+import com.google.gerrit.server.change.PutDescription;
import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.RebaseUtil;
import com.google.gerrit.server.change.Reviewed;
@@ -118,6 +120,8 @@
private final TestSubmitType testSubmitType;
private final TestSubmitType.Get getSubmitType;
private final Provider<GetMergeList> getMergeList;
+ private final PutDescription putDescription;
+ private final GetDescription getDescription;
@Inject
RevisionApiImpl(GitRepositoryManager repoManager,
@@ -151,6 +155,8 @@
TestSubmitType testSubmitType,
TestSubmitType.Get getSubmitType,
Provider<GetMergeList> getMergeList,
+ PutDescription putDescription,
+ GetDescription getDescription,
@Assisted RevisionResource r) {
this.repoManager = repoManager;
this.changes = changes;
@@ -183,6 +189,8 @@
this.testSubmitType = testSubmitType;
this.getSubmitType = getSubmitType;
this.getMergeList = getMergeList;
+ this.putDescription = putDescription;
+ this.getDescription = getDescription;
this.revision = r;
}
@@ -515,4 +523,20 @@
}
};
}
+
+ @Override
+ public void description(String description) throws RestApiException {
+ PutDescription.Input in = new PutDescription.Input();
+ in.description = description;
+ try {
+ putDescription.apply(revision, in);
+ } catch (UpdateException e) {
+ throw new RestApiException("Cannot set description", e);
+ }
+ }
+
+ @Override
+ public String description() throws RestApiException {
+ return getDescription.apply(revision);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 15d39b5..e4326a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -36,6 +36,7 @@
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
import static com.google.gerrit.server.CommonConverters.toGitPerson;
+
import static java.util.stream.Collectors.toList;
import com.google.auto.value.AutoValue;
@@ -89,6 +90,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GpgException;
@@ -192,6 +194,7 @@
private final ChangeResource.Factory changeResourceFactory;
private final ChangeKindCache changeKindCache;
private final ChangeIndexCollection indexes;
+ private final ApprovalsUtil approvalsUtil;
private boolean lazyLoad = true;
private AccountLoader accountLoader;
@@ -221,6 +224,7 @@
ChangeResource.Factory changeResourceFactory,
ChangeKindCache changeKindCache,
ChangeIndexCollection indexes,
+ ApprovalsUtil approvalsUtil,
@Assisted Set<ListChangesOption> options) {
this.db = db;
this.labelNormalizer = ln;
@@ -244,6 +248,7 @@
this.changeResourceFactory = changeResourceFactory;
this.changeKindCache = changeKindCache;
this.indexes = indexes;
+ this.approvalsUtil = approvalsUtil;
this.options = options.isEmpty()
? EnumSet.noneOf(ListChangesOption.class)
: EnumSet.copyOf(options);
@@ -891,10 +896,12 @@
private Map<String, Collection<String>> permittedLabels(ChangeControl ctl, ChangeData cd)
throws OrmException {
- if (ctl == null) {
+ if (ctl == null || !ctl.getUser().isIdentifiedUser()) {
return null;
}
+ Map<String, Short> labels = null;
+ boolean isMerged = ctl.getChange().getStatus() == Change.Status.MERGED;
LabelTypes labelTypes = ctl.getLabelTypes();
SetMultimap<String, String> permitted = LinkedHashMultimap.create();
for (SubmitRecord rec : submitRecords(cd)) {
@@ -903,12 +910,20 @@
}
for (SubmitRecord.Label r : rec.labels) {
LabelType type = labelTypes.byLabel(r.label);
- if (type == null) {
+ if (type == null || (isMerged && !type.allowPostSubmit())) {
continue;
}
PermissionRange range = ctl.getRange(Permission.forLabel(r.label));
for (LabelValue v : type.getValues()) {
- if (range.contains(v.getValue())) {
+ boolean ok = range.contains(v.getValue());
+ if (isMerged) {
+ if (labels == null) {
+ labels = currentLabels(ctl);
+ }
+ short prev = labels.getOrDefault(type.getName(), (short) 0);
+ ok &= v.getValue() >= prev;
+ }
+ if (ok) {
permitted.put(r.label, v.formatValue());
}
}
@@ -928,6 +943,17 @@
return permitted.asMap();
}
+ private Map<String, Short> currentLabels(ChangeControl ctl)
+ throws OrmException {
+ Map<String, Short> result = new HashMap<>();
+ for (PatchSetApproval psa : approvalsUtil.byPatchSetUser(
+ db.get(), ctl, ctl.getChange().currentPatchSetId(),
+ ctl.getUser().getAccountId())) {
+ result.put(psa.getLabel(), psa.getValue());
+ }
+ return result;
+ }
+
private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd,
Map<PatchSet.Id, PatchSet> map)
throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDescription.java
new file mode 100644
index 0000000..b8a34d2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDescription.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetDescription implements RestReadView<RevisionResource> {
+ @Override
+ public String apply(RevisionResource rsrc) {
+ return Strings.nullToEmpty(rsrc.getPatchSet().getDescription());
+ }
+}
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 9ff9833..4d32222 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
@@ -104,6 +104,8 @@
get(REVISION_KIND, "preview_submit").to(PreviewSubmit.class);
post(REVISION_KIND, "submit").to(Submit.class);
post(REVISION_KIND, "rebase").to(Rebase.class);
+ put(REVISION_KIND, "description").to(PutDescription.class);
+ get(REVISION_KIND, "description").to(GetDescription.class);
get(REVISION_KIND, "patch").to(GetPatch.class);
get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class);
post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index e711181..cd62e45 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -900,10 +900,27 @@
// make it possible to take a merged change and make it no longer
// submittable.
List<PatchSetApproval> reduced = new ArrayList<>(ups.size() + del.size());
- reduced.addAll(del);
+ List<String> disallowed =
+ new ArrayList<>(labelTypes.getLabelTypes().size());
+
+ for (PatchSetApproval psa : del) {
+ LabelType lt = checkNotNull(labelTypes.byLabel(psa.getLabel()));
+ String normName = lt.getName();
+ if (!lt.allowPostSubmit()) {
+ disallowed.add(normName);
+ }
+ Short prev = previous.get(normName);
+ if (prev != null && prev != 0) {
+ reduced.add(psa);
+ }
+ }
+
for (PatchSetApproval psa : ups) {
LabelType lt = checkNotNull(labelTypes.byLabel(psa.getLabel()));
String normName = lt.getName();
+ if (!lt.allowPostSubmit()) {
+ disallowed.add(normName);
+ }
Short prev = previous.get(normName);
if (prev == null) {
continue;
@@ -918,6 +935,12 @@
}
}
+ if (!disallowed.isEmpty()) {
+ throw new ResourceConflictException(
+ "Voting on labels disallowed after submit: "
+ + disallowed.stream().distinct().sorted()
+ .collect(joining(", ")));
+ }
if (!reduced.isEmpty()) {
throw new ResourceConflictException(
"Cannot reduce vote on labels for closed change: "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
new file mode 100644
index 0000000..b8224a8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.Collections;
+
+@Singleton
+public class PutDescription implements RestModifyView<RevisionResource,
+ PutDescription.Input>, UiAction<RevisionResource> {
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeMessagesUtil cmUtil;
+ private final BatchUpdate.Factory batchUpdateFactory;
+ private final PatchSetUtil psUtil;
+
+ public static class Input {
+ @DefaultInput
+ public String description;
+ }
+
+ @Inject
+ PutDescription(Provider<ReviewDb> dbProvider,
+ ChangeMessagesUtil cmUtil,
+ BatchUpdate.Factory batchUpdateFactory,
+ PatchSetUtil psUtil) {
+ this.dbProvider = dbProvider;
+ this.cmUtil = cmUtil;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.psUtil = psUtil;
+ }
+
+ @Override
+ public Response<String> apply(RevisionResource rsrc, Input input)
+ throws UpdateException, RestApiException {
+ ChangeControl ctl = rsrc.getControl();
+ if (!ctl.canEditDescription()) {
+ throw new AuthException("changing description not permitted");
+ }
+ Op op =
+ new Op(input != null ? input : new Input(), rsrc.getPatchSet().getId());
+ try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
+ rsrc.getChange().getProject(), ctl.getUser(), TimeUtil.nowTs())) {
+ u.addOp(rsrc.getChange().getId(), op);
+ u.execute();
+ }
+ return Strings.isNullOrEmpty(op.newDescription) ? Response.none()
+ : Response.ok(op.newDescription);
+ }
+
+ private class Op extends BatchUpdate.Op {
+ private final Input input;
+ private final PatchSet.Id psId;
+
+ private String oldDescription;
+ private String newDescription;
+
+ Op(Input input, PatchSet.Id psId) {
+ this.input = input;
+ this.psId = psId;
+ }
+
+ @Override
+ public boolean updateChange(ChangeContext ctx) throws OrmException {
+ PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+ ChangeUpdate update = ctx.getUpdate(psId);
+ newDescription = Strings.nullToEmpty(input.description);
+ oldDescription = Strings.nullToEmpty(ps.getDescription());
+ if (oldDescription.equals(newDescription)) {
+ return false;
+ }
+ String summary;
+ if (oldDescription.isEmpty()) {
+ summary = "Description set to \"" + newDescription + "\"";
+ } else if (newDescription.isEmpty()) {
+ summary = "Description \"" + oldDescription + "\" removed";
+ } else {
+ summary = "Description changed to \"" + newDescription + "\"";
+ }
+
+ ps.setDescription(newDescription);
+ update.setPsDescription(newDescription);
+
+ ctx.getDb().patchSets().update(Collections.singleton(ps));
+
+ ChangeMessage cmsg =
+ ChangeMessagesUtil.newMessage(ctx.getDb(), psId, ctx.getUser(),
+ ctx.getWhen(), summary, ChangeMessagesUtil.TAG_SET_DESCRIPTION);
+ cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+ return true;
+ }
+ }
+
+ @Override
+ public UiAction.Description getDescription(RevisionResource rsrc) {
+ return new UiAction.Description().setLabel("Edit Description")
+ .setVisible(rsrc.getControl().canEditDescription());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 3953f27..bc2b4df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -1029,10 +1029,14 @@
}
private ChangeContext newChangeContext(ReviewDb db, Repository repo,
- RevWalk rw, Change.Id id) throws Exception {
+ RevWalk rw, Change.Id id) throws OrmException, NoSuchChangeException {
Change c = newChanges.get(id);
if (c == null) {
c = ChangeNotes.readOneReviewDbChange(db, id);
+ if (c == null) {
+ logDebug("Failed to get change {} from unwrapped db", id);
+ throw new NoSuchChangeException(id);
+ }
}
// Pass in preloaded change to controlFor, to avoid:
// - reading from a db that does not belong to this update
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LockFailureException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LockFailureException.java
new file mode 100644
index 0000000..7380b0a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LockFailureException.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import java.io.IOException;
+
+/** Thrown when updating a ref in Git fails with LOCK_FAILURE. */
+public class LockFailureException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ public LockFailureException(String message) {
+ super(message);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 90edfb1..025db40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -679,7 +679,10 @@
rw.sort(RevSort.REVERSE, true);
rw.markStart(mergeTip);
for (RevCommit c : alreadyAccepted) {
- rw.markUninteresting(c);
+ // If branch was not created by this submit.
+ if (c != mergeTip) {
+ rw.markUninteresting(c);
+ }
}
CodeReviewCommit c;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 5421421..f3ed9f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -144,6 +144,7 @@
private static final String KEY_FUNCTION = "function";
private static final String KEY_DEFAULT_VALUE = "defaultValue";
private static final String KEY_COPY_MIN_SCORE = "copyMinScore";
+ private static final String KEY_ALLOW_POST_SUBMIT = "allowPostSubmit";
private static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
private static final String KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE = "copyAllScoresOnMergeFirstParentUpdate";
private static final String KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE = "copyAllScoresOnTrivialRebase";
@@ -802,6 +803,9 @@
KEY_DEFAULT_VALUE, dv, name)));
}
}
+ label.setAllowPostSubmit(
+ rc.getBoolean(LABEL, name, KEY_ALLOW_POST_SUBMIT,
+ LabelType.DEF_ALLOW_POST_SUBMIT));
label.setCopyMinScore(
rc.getBoolean(LABEL, name, KEY_COPY_MIN_SCORE,
LabelType.DEF_COPY_MIN_SCORE));
@@ -1197,6 +1201,8 @@
rc.setString(LABEL, name, KEY_FUNCTION, label.getFunctionName());
rc.setInt(LABEL, name, KEY_DEFAULT_VALUE, label.getDefaultValue());
+ setBooleanConfigKey(rc, name, KEY_ALLOW_POST_SUBMIT, label.allowPostSubmit(),
+ LabelType.DEF_ALLOW_POST_SUBMIT);
setBooleanConfigKey(rc, name, KEY_COPY_MIN_SCORE, label.isCopyMinScore(),
LabelType.DEF_COPY_MIN_SCORE);
setBooleanConfigKey(rc, name, KEY_COPY_MAX_SCORE, label.isCopyMaxScore(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index 5952602..6448d06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -32,13 +32,15 @@
public class RebaseSorter {
private final CodeReviewRevWalk rw;
private final RevFlag canMergeFlag;
- private final Set<RevCommit> accepted;
+ private final RevCommit initialTip;
+ private final Set<RevCommit> alreadyAccepted;
- public RebaseSorter(CodeReviewRevWalk rw, Set<RevCommit> alreadyAccepted,
- RevFlag canMergeFlag) {
+ public RebaseSorter(CodeReviewRevWalk rw, RevCommit initialTip,
+ Set<RevCommit> alreadyAccepted, RevFlag canMergeFlag) {
this.rw = rw;
this.canMergeFlag = canMergeFlag;
- this.accepted = alreadyAccepted;
+ this.initialTip = initialTip;
+ this.alreadyAccepted = alreadyAccepted;
}
public List<CodeReviewCommit> sort(Collection<CodeReviewCommit> incoming)
@@ -50,17 +52,18 @@
rw.resetRetain(canMergeFlag);
rw.markStart(n);
- for (RevCommit c : accepted) {
- // n also tip of directly pushed branch => n remains 'interesting' here
- if (!c.equals(n)) {
- rw.markUninteresting(c);
- }
+ if (initialTip != null) {
+ rw.markUninteresting(initialTip);
}
CodeReviewCommit c;
final List<CodeReviewCommit> contents = new ArrayList<>();
while ((c = rw.next()) != null) {
if (!c.has(canMergeFlag) || !incoming.contains(c)) {
+ if (isAlreadyMerged(c)) {
+ rw.markUninteresting(c);
+ break;
+ }
// We cannot merge n as it would bring something we
// aren't permitted to merge at this time. Drop n.
//
@@ -86,6 +89,21 @@
return sorted;
}
+ private boolean isAlreadyMerged(CodeReviewCommit commit) throws IOException {
+ try (CodeReviewRevWalk mirw =
+ CodeReviewCommit.newRevWalk(rw.getObjectReader())) {
+ mirw.reset();
+ mirw.markStart(commit);
+ for (RevCommit accepted : alreadyAccepted) {
+ if (mirw.isMergedInto(mirw.parseCommit(accepted),
+ mirw.parseCommit(commit))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static <T> T removeOne(final Collection<T> c) {
final Iterator<T> i = c.iterator();
final T r = i.next();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 6334cd2..f7752dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -347,9 +347,11 @@
case FORCED:
update.fireGitRefUpdatedEvent(ru);
return;
+ case LOCK_FAILURE:
+ throw new LockFailureException("Cannot delete " + ru.getName()
+ + " in " + db.getDirectory() + ": " + ru.getResult());
case FAST_FORWARD:
case IO_FAILURE:
- case LOCK_FAILURE:
case NEW:
case NOT_ATTEMPTED:
case NO_CHANGE:
@@ -424,9 +426,11 @@
revision = rw.parseCommit(ru.getNewObjectId());
update.fireGitRefUpdatedEvent(ru);
return revision;
+ case LOCK_FAILURE:
+ throw new LockFailureException("Cannot update " + ru.getName()
+ + " in " + db.getDirectory() + ": " + ru.getResult());
case FORCED:
case IO_FAILURE:
- case LOCK_FAILURE:
case NOT_ATTEMPTED:
case NO_CHANGE:
case REJECTED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
index c93cf6d..9beae1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -39,6 +40,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ReceiveCommand;
import java.io.IOException;
@@ -60,7 +62,7 @@
@Override
public List<SubmitStrategyOp> buildOps(
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
- List<CodeReviewCommit> sorted = sort(toMerge);
+ List<CodeReviewCommit> sorted = sort(toMerge, args.mergeTip.getCurrentTip());
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
boolean first = true;
@@ -256,8 +258,10 @@
args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
}
+ RevCommit initialTip = mergeTip.getInitialTip();
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- mergeTip.getCurrentTip(), args.alreadyAccepted);
+ mergeTip.getCurrentTip(), initialTip == null ?
+ ImmutableSet.<RevCommit>of() : ImmutableSet.of(initialTip));
acceptMergeTip(mergeTip);
}
}
@@ -266,11 +270,11 @@
args.alreadyAccepted.add(mergeTip.getCurrentTip());
}
- private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
- throws IntegrationException {
+ private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort,
+ RevCommit initialTip) throws IntegrationException {
try {
- return new RebaseSorter(
- args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
+ return new RebaseSorter(args.rw, initialTip, args.alreadyAccepted,
+ args.canMergeFlag).sort(toSort);
} catch (IOException e) {
throw new IntegrationException("Commit sorting failed", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 85c3d15..a53829e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -326,12 +326,6 @@
continue;
}
}
- if (!isAdmin) {
- GroupControl c = groupControlFactory.controlFor(group);
- if (!c.isVisible()) {
- continue;
- }
- }
if (visibleToAll && !group.isVisibleToAll()) {
continue;
}
@@ -339,6 +333,12 @@
&& !groupsToInspect.contains(group.getGroupUUID())) {
continue;
}
+ if (!isAdmin) {
+ GroupControl c = groupControlFactory.controlFor(group);
+ if (!c.isVisible()) {
+ continue;
+ }
+ }
filteredGroups.add(group);
}
Collections.sort(filteredGroups, new GroupComparator());
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 f7ebd77..145d1c8 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
@@ -441,6 +441,18 @@
return getRefControl().canForceEditTopicName();
}
+ /** Can this user edit the description? */
+ public boolean canEditDescription() {
+ if (getChange().getStatus().isOpen()) {
+ return isOwner() // owner (aka creator) of the change can edit desc
+ || getRefControl().isOwner() // branch owner can edit desc
+ || getProjectControl().isOwner() // project owner can edit desc
+ || getUser().getCapabilities().canAdministrateServer() // site administers are god
+ ;
+ }
+ return false;
+ }
+
public boolean canEditAssignee() {
return isOwner()
|| getProjectControl().isOwner()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 3387f06..21896f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -1129,7 +1129,7 @@
return Collections.emptySet();
}
draftsByUser = new HashSet<>();
- for (Comment sc : commentsUtil.draftByChange(db, notes)) {
+ for (Comment sc : commentsUtil.draftByChange(db, notes())) {
draftsByUser.add(sc.author.getId());
}
}
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 3e3ec13..a1a5fbc 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
@@ -374,6 +374,9 @@
assertQuery("owner:" + userId.get(), change1);
assertQuery("owner:" + user2, change2);
+
+ String nameEmail = user.asIdentifiedUser().getNameEmail();
+ assertQuery("owner: \"" + nameEmail + "\"", change1);
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 038abda..70493e8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo;
@@ -52,4 +53,15 @@
assertQuery("message:one.two", change2);
assertQuery("message:one two", change2);
}
+
+ @Test
+ public void byOwnerInvalidQuery() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo), userId);
+ String nameEmail = user.asIdentifiedUser().getNameEmail();
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("Cannot create full-text query with value: \\");
+ assertQuery("owner: \"" + nameEmail + "\"\\", change1);
+ }
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index d658b7f..93a508c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -96,8 +96,8 @@
}
private List<ChangeControl> changeFromNotesFactory(String id, CurrentUser currentUser)
- throws OrmException {
- return changeNotesFactory.create(db, Arrays.asList(Change.Id.parse(id)))
+ throws OrmException, UnloggedFailure {
+ return changeNotesFactory.create(db, parseId(id))
.stream()
.map(changeNote -> controlForChange(changeNote, currentUser))
.filter(changeControl -> changeControl.isPresent())
@@ -105,7 +105,16 @@
.collect(toList());
}
- private Optional<ChangeControl> controlForChange(ChangeNotes change, CurrentUser user) {
+ private List<Change.Id> parseId(String id) throws UnloggedFailure {
+ try {
+ return Arrays.asList(new Change.Id(Integer.parseInt(id)));
+ } catch (NumberFormatException e) {
+ throw new UnloggedFailure(2, "Invalid change ID " + id, e);
+ }
+ }
+
+ private Optional<ChangeControl> controlForChange(ChangeNotes change,
+ CurrentUser user) {
try {
return Optional.of(changeControlFactory.controlFor(change, user));
} catch (NoSuchChangeException e) {
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior.html
index f636650..ba7d3de1 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior.html
@@ -14,69 +14,30 @@
limitations under the License.
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
+<link rel="import" href="../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
+
<script>
(function(window) {
'use strict';
- /** @polymerBehavior Gerrit.KeyboardShortcutBehavior */
- var KeyboardShortcutBehavior = {
- // Set of identifiers currently blocking keyboard shortcuts. Stored as
- // a map of string to the value of true.
- _disablers: {},
-
- properties: {
- keyEventTarget: {
- type: Object,
- value: function() { return this; },
- },
-
- _boundKeyHandler: {
- type: Function,
- readonly: true,
- value: function() { return this._handleKey.bind(this); },
- },
- },
-
- attached: function() {
- this.keyEventTarget.addEventListener('keydown', this._boundKeyHandler);
- },
-
- detached: function() {
- this.keyEventTarget.removeEventListener('keydown', this._boundKeyHandler);
- },
-
+ var KeyboardShortcutBehaviorImpl = {
shouldSuppressKeyboardShortcut: function(e) {
- for (var c in KeyboardShortcutBehavior._disablers) {
- if (KeyboardShortcutBehavior._disablers[c] === true) {
- return true;
- }
+ e = Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
+ if (e.path[0].tagName === 'INPUT' || e.path[0].tagName === 'TEXTAREA') {
+ return true;
}
- var getModifierState = e.getModifierState ?
- e.getModifierState.bind(e) :
- function() { return false; };
- var target = e.detail ? e.detail.keyboardEvent : e.target;
- return getModifierState('Control') ||
- getModifierState('Alt') ||
- getModifierState('Meta') ||
- getModifierState('Fn') ||
- target.tagName == 'INPUT' ||
- target.tagName == 'TEXTAREA' ||
- target.tagName == 'SELECT' ||
- target.tagName == 'BUTTON' ||
- target.tagName == 'A' ||
- target.tagName == 'GR-BUTTON';
- },
-
- disable: function(id) {
- KeyboardShortcutBehavior._disablers[id] = true;
- },
-
- enable: function(id) {
- delete KeyboardShortcutBehavior._disablers[id];
+ for (var i = 0; i < e.path.length; i++) {
+ if (e.path[i].tagName === 'GR-OVERLAY') { return true; }
+ }
+ return false;
},
};
window.Gerrit = window.Gerrit || {};
- window.Gerrit.KeyboardShortcutBehavior = KeyboardShortcutBehavior;
+ /** @polymerBehavior Gerrit.KeyboardShortcutBehavior */
+ window.Gerrit.KeyboardShortcutBehavior = [
+ Polymer.IronA11yKeysBehavior,
+ KeyboardShortcutBehaviorImpl,
+ ];
})(window);
</script>
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior_test.html
index 5ec4145..457c5f25 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior_test.html
@@ -30,49 +30,76 @@
</template>
</test-fixture>
+<test-fixture id="within-overlay">
+ <template>
+ <gr-overlay>
+ <test-element></test-element>
+ </gr-overlay>
+ </template>
+</test-fixture>
+
<script>
suite('keyboard-shortcut-behavior tests', function() {
var element;
+ var overlay;
suiteSetup(function() {
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
behaviors: [Gerrit.KeyboardShortcutBehavior],
- properties: {
- keyEventTarget: {
- value: function() { return document.body; },
- },
- log: {
- value: function() { return []; },
- },
+ keyBindings: {
+ 'k': '_handleKey'
},
-
- _handleKey: function(e) {
- if (!this.shouldSuppressKeyboardShortcut(e)) {
- this.log.push(e.keyCode);
- }
- },
+ _handleKey: function() {},
});
});
setup(function() {
element = fixture('basic');
+ overlay = fixture('within-overlay');
});
- test('blocks keydown events iff one or more disablers', function() {
- MockInteractions.pressAndReleaseKeyOn(document.body, 97); // 'a'
- Gerrit.KeyboardShortcutBehavior.enable('x'); // should have no effect
- MockInteractions.pressAndReleaseKeyOn(document.body, 98); // 'b'
- Gerrit.KeyboardShortcutBehavior.disable('x'); // blocking starts here
- MockInteractions.pressAndReleaseKeyOn(document.body, 99); // 'c'
- Gerrit.KeyboardShortcutBehavior.disable('y');
- MockInteractions.pressAndReleaseKeyOn(document.body, 100); // 'd'
- Gerrit.KeyboardShortcutBehavior.enable('x');
- MockInteractions.pressAndReleaseKeyOn(document.body, 101); // 'e'
- Gerrit.KeyboardShortcutBehavior.enable('y'); // blocking ends here
- MockInteractions.pressAndReleaseKeyOn(document.body, 102); // 'f'
- assert.deepEqual(element.log, [97, 98, 102]);
+ test('doesn’t block kb shortcuts for non-whitelisted els', function(done) {
+ var divEl = document.createElement('div');
+ element.appendChild(divEl);
+ element._handleKey = function(e) {
+ assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
+ done();
+ };
+ MockInteractions.keyDownOn(divEl, 75, null, 'k');
});
+
+ test('blocks kb shortcuts for input els', function(done) {
+ var inputEl = document.createElement('input');
+ element.appendChild(inputEl);
+ element._handleKey = function(e) {
+ assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
+ done();
+ };
+ MockInteractions.keyDownOn(inputEl, 75, null, 'k');
+ });
+
+ test('blocks kb shortcuts for textarea els', function(done) {
+ var textareaEl = document.createElement('textarea');
+ element.appendChild(textareaEl);
+ element._handleKey = function(e) {
+ assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
+ done();
+ };
+ MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
+ });
+
+ test('blocks kb shortcuts for anything in a gr-overlay', function(done) {
+ var divEl = document.createElement('div');
+ var element = overlay.querySelector('test-element');
+ element.appendChild(divEl);
+ element._handleKey = function(e) {
+ assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
+ done();
+ };
+ MockInteractions.keyDownOn(divEl, 75, null, 'k');
+ });
+
});
</script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index 2be0afc..51626cc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -78,6 +78,12 @@
Gerrit.RESTClientBehavior,
],
+ keyBindings: {
+ 'j': '_handleJKey',
+ 'k': '_handleKKey',
+ 'o enter': '_handleEnterKey',
+ },
+
attached: function() {
this._loadPreferences();
},
@@ -149,31 +155,37 @@
account._account_id != change.owner._account_id;
},
- _handleKey: function(e) {
- if (this.shouldSuppressKeyboardShortcut(e)) { return; }
-
- if (this.groups == null) { return; }
+ _getAggregateGroupsLen: function(groups) {
+ groups = groups || [];
var len = 0;
this.groups.forEach(function(group) {
len += group.length;
});
- switch (e.keyCode) {
- case 74: // 'j'
- e.preventDefault();
- if (this.selectedIndex == len - 1) { return; }
- this.selectedIndex += 1;
- break;
- case 75: // 'k'
- e.preventDefault();
- if (this.selectedIndex == 0) { return; }
- this.selectedIndex -= 1;
- break;
- case 79: // 'o'
- case 13: // 'enter'
- e.preventDefault();
- page.show(this._changeURLForIndex(this.selectedIndex));
- break;
- }
+ return len;
+ },
+
+ _handleJKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ var len = this._getAggregateGroupsLen(this.groups);
+ if (this.selectedIndex === len - 1) { return; }
+ this.selectedIndex += 1;
+ },
+
+ _handleKKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (this.selectedIndex === 0) { return; }
+ this.selectedIndex -= 1;
+ },
+
+ _handleEnterKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ page.show(this._changeURLForIndex(this.selectedIndex));
},
_changeURLForIndex: function(index) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 718cfb5..33b4279 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -131,25 +131,25 @@
flush(function() {
assert.isTrue(elementItems[0].selected);
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sinon.stub(page, 'show');
assert.equal(element.selectedIndex, 2);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWithExactly('/c/2/'),
'Should navigate to /c/2/');
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWithExactly('/c/1/'),
'Should navigate to /c/1/');
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 0);
showStub.restore();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 30e9e86..9c591db 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -76,7 +76,7 @@
on-tap="_handleActionTap"></gr-button>
</template>
</section>
- <section hidden$="[[!_actionCount(_revisionActions.*, _additionalActions.*)]]">
+ <section hidden$="[[!_actionCount(revisionActions.*, _additionalActions.*)]]">
<template is="dom-repeat" items="[[_revisionActionValues]]" as="action">
<gr-button title$="[[action.title]]"
hidden$="[[_computeActionHidden(action.__key, _hiddenRevisionActions.*)]]"
@@ -98,7 +98,9 @@
hidden></gr-confirm-rebase-dialog>
<gr-confirm-cherrypick-dialog id="confirmCherrypick"
class="confirmDialog"
- message="[[commitMessage]]"
+ change-status="[[changeStatus]]"
+ commit-message="[[commitMessage]]"
+ commit-num="[[commitNum]]"
on-confirm="_handleCherrypickConfirm"
on-cancel="_handleConfirmDialogCancel"
hidden></gr-confirm-cherrypick-dialog>
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 ef2a3b4..2d5c0a7 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
@@ -74,23 +74,25 @@
},
},
changeNum: String,
+ changeStatus: String,
+ commitNum: String,
patchNum: String,
commitMessage: {
type: String,
value: '',
},
+ revisionActions: {
+ type: Object,
+ value: function() { return {}; },
+ },
_loading: {
type: Boolean,
value: true,
},
- _revisionActions: {
- type: Object,
- value: function() { return {}; },
- },
_revisionActionValues: {
type: Array,
- computed: '_computeRevisionActionValues(_revisionActions.*, ' +
+ computed: '_computeRevisionActionValues(revisionActions.*, ' +
'primaryActionKeys.*, _additionalActions.*)',
},
_changeActionValues: {
@@ -121,7 +123,7 @@
],
observers: [
- '_actionsChanged(actions.*, _revisionActions.*, _additionalActions.*)',
+ '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*)',
],
ready: function() {
@@ -137,7 +139,7 @@
return this._getRevisionActions().then(function(revisionActions) {
if (!revisionActions) { return; }
- this._revisionActions = revisionActions;
+ this.revisionActions = revisionActions;
this._loading = false;
}.bind(this)).catch(function(err) {
alert('Couldn’t load revision actions. Check the console ' +
@@ -363,7 +365,7 @@
/* falls through */ // required by JSHint
default:
this._fireAction(this._prependSlash(key),
- this._revisionActions[key], true);
+ this.revisionActions[key], true);
}
},
@@ -400,7 +402,7 @@
}
this.$.overlay.close();
el.hidden = true;
- this._fireAction('/rebase', this._revisionActions.rebase, true, payload);
+ this._fireAction('/rebase', this.revisionActions.rebase, true, payload);
},
_handleCherrypickConfirm: function() {
@@ -418,7 +420,7 @@
el.hidden = true;
this._fireAction(
'/cherrypick',
- this._revisionActions.cherrypick,
+ this.revisionActions.cherrypick,
true,
{
destination: el.branch,
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 af666b0..15fcd8e 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
@@ -248,7 +248,10 @@
<gr-change-actions id="actions"
change="[[_change]]"
actions="[[_change.actions]]"
+ revision-actions="[[_currentRevisionActions]]"
change-num="[[_changeNum]]"
+ change-status="[[_change.status]]"
+ commit-num="[[_commitInfo.commit]]"
patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"
commit-message="[[_latestCommitMessage]]"
on-reload-change="_handleReloadChange"></gr-change-actions>
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 bbd4d2d..8ce963e 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
@@ -55,6 +55,7 @@
observer: '_changeChanged',
},
_commitInfo: Object,
+ _files: Object,
_changeNum: String,
_diffDrafts: {
type: Object,
@@ -77,6 +78,7 @@
type: Object,
observer: '_updateSelected',
},
+ _currentRevisionActions: Object,
_allPatchSets: {
type: Array,
computed: '_computeAllPatchSets(_change)',
@@ -110,6 +112,13 @@
'_paramsAndChangeChanged(params, _change)',
],
+ keyBindings: {
+ 'shift+r': '_handleCapitalRKey',
+ 'a': '_handleAKey',
+ 'd': '_handleDKey',
+ 'u': '_handleUKey',
+ },
+
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn;
@@ -579,30 +588,29 @@
}.bind(this));
},
- _handleKey: function(e) {
+ _handleAKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- switch (e.keyCode) {
- case 65: // 'a'
- if (this._loggedIn && !e.shiftKey) {
- e.preventDefault();
- this._openReplyDialog();
- }
- break;
- case 68: // 'd'
- e.preventDefault();
- this.$.downloadOverlay.open();
- break;
- case 82: // 'r'
- if (e.shiftKey) {
- e.preventDefault();
- this._switchToMostRecentPatchNum();
- }
- break;
- case 85: // 'u'
- e.preventDefault();
- this._determinePageBack();
- break;
- }
+ if (!this._loggedIn || e.detail.keyboardEvent.shiftKey) { return; }
+ e.preventDefault();
+ this._openReplyDialog();
+ },
+
+ _handleDKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ e.preventDefault();
+ this.$.downloadOverlay.open();
+ },
+
+ _handleCapitalRKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ e.preventDefault();
+ this._switchToMostRecentPatchNum();
+ },
+
+ _handleUKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ e.preventDefault();
+ this._determinePageBack();
},
_determinePageBack: function() {
@@ -681,7 +689,16 @@
if (!change.reviewer_updates) {
change.reviewer_updates = null;
}
+ var currentRevision = change.revisions &&
+ change.revisions[change.current_revision];
+ this._latestCommitMessage = currentRevision.commit.message;
this._change = change;
+ if (!this._patchRange || !this._patchRange.patchNum ||
+ this._patchRange.patchNum === currentRevision._number) {
+ this._commitInfo = currentRevision.commit;
+ this._currentRevisionActions = currentRevision.actions;
+ // TODO: Fetch and process files.
+ }
}.bind(this));
},
@@ -728,41 +745,26 @@
var detailCompletes = this._getChangeDetail().then(function() {
this._loading = false;
+ this._getProjectConfig();
}.bind(this));
this._getComments();
if (this._patchRange.patchNum) {
- return this._reloadPatchNumDependentResources().then(function() {
- return detailCompletes;
- }).then(function() {
- return this._reloadDetailDependentResources();
+ return Promise.all([
+ this._reloadPatchNumDependentResources(),
+ detailCompletes,
+ ]).then(function() {
+ return this.$.actions.reload();
}.bind(this));
} else {
// The patch number is reliant on the change detail request.
return detailCompletes.then(function() {
- return this._reloadPatchNumDependentResources();
- }.bind(this)).then(function() {
- return this._reloadDetailDependentResources();
+ this.$.fileList.reload();
}.bind(this));
}
},
/**
- * Kicks off requests for resources that rely on the change detail
- * (`this._change`) being loaded.
- */
- _reloadDetailDependentResources: function() {
- if (!this._change) { return Promise.resolve(); }
-
- return this._getProjectConfig().then(function() {
- return Promise.all([
- this._getLatestCommitMessage(),
- this.$.actions.reload(),
- ]);
- }.bind(this));
- },
-
- /**
* Kicks off requests for resources that rely on the patch range
* (`this._patchRange`) being defined.
*/
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 7c8a329..c40062b 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
@@ -53,27 +53,27 @@
suite('keyboard shortcuts', function() {
test('U should navigate to / if no backPage set', function() {
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/'));
});
test('U should navigate to backPage if set', function() {
element.backPage = '/dashboard/self';
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/dashboard/self'));
});
test('A should toggle overlay', function() {
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
var overlayEl = element.$.replyOverlay;
assert.isFalse(overlayEl.opened);
element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isFalse(overlayEl.opened);
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(overlayEl.opened);
overlayEl.close();
assert.isFalse(overlayEl.opened);
@@ -102,7 +102,7 @@
return Promise.resolve({
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
revisions: {
- rev1: {_number: 1},
+ rev1: {_number: 1, commit: {}},
rev13: {_number: 13},
},
current_revision: 'rev1',
@@ -117,13 +117,12 @@
done();
});
- // 'shift + R'
- MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift');
+ MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift', 'r');
});
test('d should open download overlay', function() {
var stub = sandbox.stub(element.$.downloadOverlay, 'open');
- MockInteractions.pressAndReleaseKeyOn(element, 68); // 'd'
+ MockInteractions.pressAndReleaseKeyOn(element, 68, null, 'd');
assert.isTrue(stub.called);
});
});
@@ -468,7 +467,12 @@
test('topic is coalesced to null', function(done) {
sandbox.stub(element, '_changeChanged');
sandbox.stub(element.$.restAPI, 'getChangeDetail', function() {
- return Promise.resolve({id: '123456789', labels: {}});
+ return Promise.resolve({
+ id: '123456789',
+ labels: {},
+ current_revision: 'foo',
+ revisions: {foo: {commit: {}}},
+ });
});
element._getChangeDetail().then(function() {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
index ebc6533..5d2a9ac 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
@@ -74,7 +74,7 @@
autocomplete="on"
rows="4"
max-rows="15"
- bind-value="{{message}}"></iron-autogrow-textarea>
+ bind-value="{{_message}}"></iron-autogrow-textarea>
</div>
</gr-confirm-dialog>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
index f27e4e2..f87af1c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -31,7 +31,22 @@
properties: {
branch: String,
- message: String,
+ changeStatus: String,
+ commitMessage: String,
+ commitNum: String,
+ _message: {
+ type: String,
+ computed: '_computeMessage(changeStatus, commitNum, commitMessage)',
+ },
+ },
+
+ _computeMessage: function(changeStatus, commitNum, commitMessage) {
+ var newMessage = commitMessage;
+
+ if (changeStatus === 'MERGED') {
+ newMessage += '(cherry picked from commit ' + commitNum + ')';
+ }
+ return newMessage;
},
_handleConfirmTap: function(e) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
new file mode 100644
index 0000000..edf7d7a
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-confirm-cherrypick-dialog</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-confirm-cherrypick-dialog.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-confirm-cherrypick-dialog></gr-confirm-cherrypick-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-confirm-cherrypick-dialog tests', function() {
+ var element;
+
+ setup(function() {
+ element = fixture('basic');
+ });
+
+ test('with merged change', function() {
+ element.changeStatus = 'MERGED';
+ element.commitMessage = 'message\n';
+ element.commitNum = '123';
+ element.branch = 'master';
+ flushAsynchronousOperations();
+ var expectedMessage = 'message\n(cherry picked from commit 123)';
+ assert.equal(element._message, expectedMessage);
+ });
+
+ test('with unmerged change', function() {
+ element.changeStatus = 'OPEN';
+ element.commitMessage = 'message\n';
+ element.commitNum = '123';
+ element.branch = 'master';
+ flushAsynchronousOperations();
+ var expectedMessage = 'message\n';
+ assert.equal(element._message, expectedMessage);
+ });
+ });
+</script>
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 873ab3b..4e68559 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
@@ -179,8 +179,7 @@
<select
id="modeSelect"
is="gr-select"
- bind-value="{{diffViewMode}}"
- on-change="_handleDropdownChange">
+ bind-value="{{diffViewMode}}">
<option value="SIDE_BY_SIDE">Side By Side</option>
<option value="UNIFIED_DIFF">Unified</option>
</select>
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 98c4284..ab889ac 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
@@ -106,6 +106,22 @@
Gerrit.URLEncodingBehavior,
],
+ keyBindings: {
+ 'shift+left': '_handleShiftLeftKey',
+ 'shift+right': '_handleShiftRightKey',
+ 'i': '_handleIKey',
+ 'shift+i': '_handleCapitalIKey',
+ 'down j': '_handleDownKey',
+ 'up k': '_handleUpKey',
+ 'c': '_handleCKey',
+ '[': '_handleLeftBracketKey',
+ ']': '_handleRightBracketKey',
+ 'o enter': '_handleEnterKey',
+ 'n': '_handleNKey',
+ 'p': '_handlePKey',
+ 'shift+a': '_handleCapitalAKey',
+ },
+
reload: function() {
if (!this.changeNum || !this.patchRange.patchNum) {
return Promise.resolve();
@@ -216,9 +232,6 @@
this.set(['_shownFiles', i, '__expanded'], true);
this.set(['_files', i, '__expanded'], true);
}
- if (e && e.target) {
- e.target.blur();
- }
},
_collapseAllDiffs: function(e) {
@@ -228,9 +241,6 @@
this.set(['_files', i, '__expanded'], false);
}
this.$.cursor.handleDiffUpdate();
- if (e && e.target) {
- e.target.blur();
- }
},
_computeCommentsString: function(comments, patchNum, path) {
@@ -298,113 +308,137 @@
});
},
- _handleKey: function(e) {
+ _handleShiftLeftKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- switch (e.keyCode) {
- case 37: // left
- if (e.shiftKey && this._showInlineDiffs) {
- e.preventDefault();
- this.$.cursor.moveLeft();
- }
- break;
- case 39: // right
- if (e.shiftKey && this._showInlineDiffs) {
- e.preventDefault();
- this.$.cursor.moveRight();
- }
- break;
- case 73: // 'i'
- if (e.shiftKey) {
- e.preventDefault();
- this._toggleInlineDiffs();
- } else if (this.selectedIndex !== undefined) {
- e.preventDefault();
- var expanded = this._files[this.selectedIndex].__expanded;
- // Until Polymer 2.0, manual management of reflection between _files
- // and _shownFiles is necessary.
- this.set(['_shownFiles', this.selectedIndex, '__expanded'],
- !expanded);
- this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
- }
- break;
- case 40: // down
- case 74: // 'j'
- e.preventDefault();
- if (this._showInlineDiffs) {
- this.$.cursor.moveDown();
- } else {
- this.selectedIndex =
- Math.min(this._numFilesShown, this.selectedIndex + 1);
- this._scrollToSelectedFile();
- }
- break;
- case 38: // up
- case 75: // 'k'
- e.preventDefault();
- if (this._showInlineDiffs) {
- this.$.cursor.moveUp();
- } else {
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
- this._scrollToSelectedFile();
- }
- break;
- case 67: // 'c'
- var isRangeSelected = this.diffs.some(function(diff) {
- return diff.isRangeSelected();
- }, this);
- if (this._showInlineDiffs && !isRangeSelected) {
- e.preventDefault();
- this._addDraftAtTarget();
- }
- break;
- case 219: // '['
- e.preventDefault();
- this._openSelectedFile(this._files.length - 1);
- break;
- case 221: // ']'
- e.preventDefault();
- this._openSelectedFile(0);
- break;
- case 13: // <enter>
- case 79: // 'o'
- e.preventDefault();
- if (this._showInlineDiffs) {
- this._openCursorFile();
- } else {
- this._openSelectedFile();
- }
- break;
- case 78: // 'n'
- if (this._showInlineDiffs) {
- e.preventDefault();
- if (e.shiftKey) {
- this.$.cursor.moveToNextCommentThread();
- } else {
- this.$.cursor.moveToNextChunk();
- }
- }
- break;
- case 80: // 'p'
- if (this._showInlineDiffs) {
- e.preventDefault();
- if (e.shiftKey) {
- this.$.cursor.moveToPreviousCommentThread();
- } else {
- this.$.cursor.moveToPreviousChunk();
- }
- }
- break;
- case 65: // 'a'
- if (e.shiftKey) { // Hide left diff.
- e.preventDefault();
- this._forEachDiff(function(diff) {
- diff.toggleLeftDiff();
- });
- }
- break;
+ if (!this._showInlineDiffs) { return; }
+
+ e.preventDefault();
+ this.$.cursor.moveLeft();
+ },
+
+ _handleShiftRightKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ if (!this._showInlineDiffs) { return; }
+
+ e.preventDefault();
+ this.$.cursor.moveRight();
+ },
+
+ _handleIKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ if (this.selectedIndex === undefined) { return; }
+
+ e.preventDefault();
+ var expanded = this._files[this.selectedIndex].__expanded;
+ // Until Polymer 2.0, manual management of reflection between _files
+ // and _shownFiles is necessary.
+ this.set(['_shownFiles', this.selectedIndex, '__expanded'],
+ !expanded);
+ this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
+ },
+
+ _handleCapitalIKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._toggleInlineDiffs();
+ },
+
+ _handleDownKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (this._showInlineDiffs) {
+ this.$.cursor.moveDown();
+ } else {
+ this.selectedIndex =
+ Math.min(this._numFilesShown, this.selectedIndex + 1);
+ this._scrollToSelectedFile();
}
},
+ _handleUpKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (this._showInlineDiffs) {
+ this.$.cursor.moveUp();
+ } else {
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
+ this._scrollToSelectedFile();
+ }
+ },
+
+ _handleCKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ var isRangeSelected = this.diffs.some(function(diff) {
+ return diff.isRangeSelected();
+ }, this);
+ if (this._showInlineDiffs && !isRangeSelected) {
+ e.preventDefault();
+ this._addDraftAtTarget();
+ }
+ },
+
+ _handleLeftBracketKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._openSelectedFile(this._files.length - 1);
+ },
+
+ _handleRightBracketKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._openSelectedFile(0);
+ },
+
+ _handleEnterKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (this._showInlineDiffs) {
+ this._openCursorFile();
+ } else {
+ this._openSelectedFile();
+ }
+ },
+
+ _handleNKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ if (!this._showInlineDiffs) { return; }
+
+ e.preventDefault();
+ if (e.shiftKey) {
+ this.$.cursor.moveToNextCommentThread();
+ } else {
+ this.$.cursor.moveToNextChunk();
+ }
+ },
+
+ _handlePKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ if (!this._showInlineDiffs) { return; }
+
+ e.preventDefault();
+ if (e.shiftKey) {
+ this.$.cursor.moveToPreviousCommentThread();
+ } else {
+ this.$.cursor.moveToPreviousChunk();
+ }
+ },
+
+ _handleCapitalAKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._forEachDiff(function(diff) {
+ diff.toggleLeftDiff();
+ });
+ },
+
_toggleInlineDiffs: function() {
if (this._showInlineDiffs) {
this._collapseAllDiffs();
@@ -555,10 +589,6 @@
return DiffViewMode.SIDE_BY_SIDE;
},
- _handleDropdownChange: function(e) {
- e.target.blur();
- },
-
_fileListActionsVisible: function(numFilesShown, maxFilesForBulkActions) {
return numFilesShown <= maxFilesForBulkActions;
},
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 b530bae..1e146b5 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
@@ -155,7 +155,7 @@
return [{toggleLeftDiff: toggleLeftDiffStub}];
},
});
- MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce);
diffsStub.restore();
});
@@ -168,25 +168,25 @@
assert.isTrue(elementItems[0].hasAttribute('selected'));
assert.isFalse(elementItems[1].hasAttribute('selected'));
assert.isFalse(elementItems[2].hasAttribute('selected'));
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J'
+ MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J'
+ MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sandbox.stub(page, 'show');
assert.equal(element.selectedIndex, 2);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'ENTER'
+ MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
'Should navigate to /c/42/2/myfile.txt');
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K'
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 79); // 'O'
+ MockInteractions.pressAndReleaseKeyOn(element, 79, null, 'o');
assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
'Should navigate to /c/42/2/file_added_in_rev2.txt');
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K'
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
+ MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 0);
showStub.restore();
@@ -194,23 +194,23 @@
test('i key shows/hides selected inline diff', function() {
element.selectedIndex = 0;
- MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I'
+ MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[0].hasAttribute('hidden'));
- MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I'
+ MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isTrue(element.diffs[0].hasAttribute('hidden'));
element.selectedIndex = 1;
- MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I'
+ MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[1].hasAttribute('hidden'));
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I'
+ MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (var index in element.diffs) {
assert.isFalse(element.diffs[index].hasAttribute('hidden'));
}
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I'
+ MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (var index in element.diffs) {
assert.isTrue(element.diffs[index].hasAttribute('hidden'));
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index cee2a69..aa0703f 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -113,17 +113,11 @@
for (var i = 0; i < messageEls.length; i++) {
messageEls[i].expanded = this._expanded;
}
- if (e && e.target) {
- e.target.blur();
- }
},
_handleAutomatedMessageToggleTap: function(e) {
e.preventDefault();
this._hideAutomated = !this._hideAutomated;
- if (e && e.target) {
- e.target.blur();
- }
},
_handleScrollTo: function(e) {
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 2c8549d..61eb280 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
@@ -94,6 +94,10 @@
'searchButton.tap': '_preventDefaultAndNavigateToInputVal',
},
+ keyBindings: {
+ '/': '_handleForwardSlashKey',
+ },
+
properties: {
value: {
type: String,
@@ -292,18 +296,12 @@
});
},
- _handleKey: function(e) {
+ _handleForwardSlashKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- switch (e.keyCode) {
- case 191: // '/' or '?' with shift key.
- // TODO(andybons): Localization using e.key/keypress event.
- if (e.shiftKey) { break; }
- e.preventDefault();
- var s = this.$.searchInput;
- s.focus();
- s.setSelectionRange(0, s.value.length);
- break;
- }
+
+ e.preventDefault();
+ this.$.searchInput.focus();
+ this.$.searchInput.selectAll();
},
});
})();
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 cd218d4..621511f 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
@@ -70,13 +70,15 @@
done();
});
element.value = 'test';
- MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13);
+ MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
+ null, 'enter');
});
test('search query should be double-escaped', function() {
var showStub = sinon.stub(page, 'show');
element.$.searchInput.text = 'fate/stay';
- MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13);
+ MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
+ null, 'enter');
assert.equal(showStub.lastCall.args[0], '/q/fate%252Fstay');
showStub.restore();
});
@@ -85,7 +87,8 @@
var showStub = sinon.stub(page, 'show');
var blurSpy = sinon.spy(element.$.searchInput.$.input, 'blur');
element.$.searchInput.text = 'fate/stay';
- MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13);
+ MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
+ null, 'enter');
assert.isTrue(blurSpy.called);
showStub.restore();
blurSpy.restore();
@@ -94,10 +97,19 @@
test('empty search query does not trigger nav', function() {
var showSpy = sinon.spy(page, 'show');
element.value = '';
- MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13);
+ MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
+ null, 'enter');
assert.isFalse(showSpy.called);
});
+ test('keyboard shortcuts', function() {
+ var focusSpy = sinon.spy(element.$.searchInput, 'focus');
+ var selectAllSpy = sinon.spy(element.$.searchInput, 'selectAll');
+ MockInteractions.pressAndReleaseKeyOn(document.body, 191, null, '/');
+ assert.isTrue(focusSpy.called);
+ assert.isTrue(selectAllSpy.called);
+ });
+
suite('_getSearchSuggestions',
function() {
setup(function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index 20b032d..a24100f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -57,6 +57,10 @@
'_commentsChanged(comments.splices)',
],
+ keyBindings: {
+ 'e shift+e': '_handleEKey',
+ },
+
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._showActions = loggedIn;
@@ -88,12 +92,12 @@
this._orderedComments = this._sortedComments(this.comments);
},
- _handleKey: function(e) {
+ _handleEKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- if (e.keyCode === 69) { // 'e'
- e.preventDefault();
- this._expandCollapseComments(e.shiftKey);
- }
+
+ // Don’t preventDefault in this case because it will render the event
+ // useless for other handlers (other gr-diff-comment-thread elements).
+ this._expandCollapseComments(e.detail.keyboardEvent.shiftKey);
},
_expandCollapseComments: function(actionIsCollapse) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 73e4983..7b85b62 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -280,10 +280,10 @@
}];
element.comments = comments;
var expandCollapseStub = sinon.stub(element, '_expandCollapseComments');
- MockInteractions.pressAndReleaseKeyOn(element, 69); // 'e'
+ MockInteractions.pressAndReleaseKeyOn(element, 69, null, 'e');
assert.isTrue(expandCollapseStub.lastCall.calledWith(false));
- MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift'); // 'e'
+ MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift', 'e');
assert.isTrue(expandCollapseStub.lastCall.calledWith(true));
expandCollapseStub.restore();
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index 410a813..720e6542 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -279,34 +279,34 @@
},
_handleReply: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
this.fire('reply', this._getEventPayload(), {bubbles: false});
},
_handleQuote: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
this.fire(
'reply', this._getEventPayload({quote: true}), {bubbles: false});
},
_handleDone: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
this.fire('done', this._getEventPayload(), {bubbles: false});
},
_handleEdit: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
this._messageText = this.comment.message;
this.editing = true;
},
_handleSave: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
this.save();
},
_handleCancel: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
if (this.comment.message == null || this.comment.message.length == 0) {
this._fireDiscard();
return;
@@ -321,7 +321,7 @@
},
_handleDiscard: function(e) {
- this._preventDefaultAndBlur(e);
+ e.preventDefault();
if (!this.comment.__draft) {
throw Error('Cannot discard a non-draft comment.');
}
@@ -345,11 +345,6 @@
}.bind(this));
},
- _preventDefaultAndBlur: function(e) {
- e.preventDefault();
- Polymer.dom(e).rootTarget.blur();
- },
-
_saveDraft: function(draft) {
return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
},
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 9dbb25b..b6471e7 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
@@ -217,8 +217,7 @@
id="modeSelect"
is="gr-select"
bind-value="{{changeViewState.diffMode}}"
- hidden$="[[_computeModeSelectHidden(_isImageDiff)]]"
- on-change="_handleDropdownChange">
+ hidden$="[[_computeModeSelectHidden(_isImageDiff)]]">
<option value="SIDE_BY_SIDE">Side By Side</option>
<option value="UNIFIED_DIFF">Unified</option>
</select>
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 44204fd..1d63b47 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
@@ -100,6 +100,22 @@
'_getFiles(_changeNum, _patchRange.*)',
],
+ keyBindings: {
+ 'esc': '_handleEscKey',
+ 'shift+left': '_handleShiftLeftKey',
+ 'shift+right': '_handleShiftRightKey',
+ 'up k': '_handleUpKey',
+ 'down j': '_handleDownKey',
+ 'c': '_handleCKey',
+ '[': '_handleLeftBracketKey',
+ ']': '_handleRightBracketKey',
+ 'n shift+n': '_handleNKey',
+ 'p shift+p': '_handlePKey',
+ 'a shift+a': '_handleAKey',
+ 'u': '_handleUKey',
+ ',': '_handleCommaKey',
+ },
+
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn;
@@ -185,103 +201,129 @@
this._patchRange.patchNum, this._path, reviewed);
},
- _checkForModifiers: function(e) {
- return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
- },
-
- _handleKey: function(e) {
+ _handleEscKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- switch (e.keyCode) {
- case 27: // escape
- e.preventDefault();
- this.$.diff.displayLine = false;
- break;
- case 37: // left
- if (e.shiftKey) {
- e.preventDefault();
- this.$.cursor.moveLeft();
- }
- break;
- case 39: // right
- if (e.shiftKey) {
- e.preventDefault();
- this.$.cursor.moveRight();
- }
- break;
- case 40: // down
- case 74: // 'j'
- e.preventDefault();
- this.$.diff.displayLine = true;
- this.$.cursor.moveDown();
- break;
- case 38: // up
- case 75: // 'k'
- e.preventDefault();
- this.$.diff.displayLine = true;
- this.$.cursor.moveUp();
- break;
- case 67: // 'c'
- if (this._checkForModifiers(e)) { return; }
- if (!this.$.diff.isRangeSelected()) {
- e.preventDefault();
- var line = this.$.cursor.getTargetLineElement();
- if (line) {
- this.$.diff.addDraftAtLine(line);
- }
- }
- break;
- case 219: // '['
- e.preventDefault();
- this._navToFile(this._path, this._fileList, -1);
- break;
- case 221: // ']'
- e.preventDefault();
- this._navToFile(this._path, this._fileList, 1);
- break;
- case 78: // 'n'
- e.preventDefault();
- if (e.shiftKey) {
- this.$.cursor.moveToNextCommentThread();
- } else {
- this.$.cursor.moveToNextChunk();
- }
- break;
- case 80: // 'p'
- e.preventDefault();
- if (e.shiftKey) {
- this.$.cursor.moveToPreviousCommentThread();
- } else {
- this.$.cursor.moveToPreviousChunk();
- }
- break;
- case 65: // 'a'
- if (e.shiftKey) { // Hide left diff.
- e.preventDefault();
- this.$.diff.toggleLeftDiff();
- break;
- }
+ e.preventDefault();
+ this.$.diff.displayLine = false;
+ },
- if (!this._loggedIn) { break; }
+ _handleShiftLeftKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- this.set('changeViewState.showReplyDialog', true);
- /* falls through */ // required by JSHint
- case 85: // 'u'
- if (this._changeNum && this._patchRange.patchNum) {
- e.preventDefault();
- page.show(this._getChangePath(
- this._changeNum,
- this._patchRange,
- this._change && this._change.revisions));
- }
- break;
- case 188: // ','
- e.preventDefault();
- this._openPrefs();
- break;
+ e.preventDefault();
+ this.$.cursor.moveLeft();
+ },
+
+ _handleShiftRightKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this.$.cursor.moveRight();
+ },
+
+ _handleUpKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this.$.diff.displayLine = true;
+ this.$.cursor.moveUp();
+ },
+
+ _handleDownKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this.$.diff.displayLine = true;
+ this.$.cursor.moveDown();
+ },
+
+ _handleCKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ if (this.$.diff.isRangeSelected()) { return; }
+
+ e.preventDefault();
+ var line = this.$.cursor.getTargetLineElement();
+ if (line) {
+ this.$.diff.addDraftAtLine(line);
}
},
+ _handleLeftBracketKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._navToFile(this._path, this._fileList, -1);
+ },
+
+ _handleRightBracketKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._navToFile(this._path, this._fileList, 1);
+ },
+
+ _handleNKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (e.detail.keyboardEvent.shiftKey) {
+ this.$.cursor.moveToNextCommentThread();
+ } else {
+ this.$.cursor.moveToNextChunk();
+ }
+ },
+
+ _handlePKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ if (e.detail.keyboardEvent.shiftKey) {
+ this.$.cursor.moveToPreviousCommentThread();
+ } else {
+ this.$.cursor.moveToPreviousChunk();
+ }
+ },
+
+ _handleAKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ if (e.detail.keyboardEvent.shiftKey) { // Hide left diff.
+ e.preventDefault();
+ this.$.diff.toggleLeftDiff();
+ return;
+ }
+
+ if (!this._loggedIn) { return; }
+
+ this.set('changeViewState.showReplyDialog', true);
+ e.preventDefault();
+ this._navToChangeView();
+ },
+
+ _handleUKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._navToChangeView();
+ },
+
+ _handleCommaKey: function(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+ e.preventDefault();
+ this._openPrefs();
+ },
+
+ _navToChangeView: function() {
+ if (!this._changeNum || !this._patchRange.patchNum) { return; }
+
+ page.show(this._getChangePath(
+ this._changeNum,
+ this._patchRange,
+ this._change && this._change.revisions));
+ },
+
_navToFile: function(path, fileList, direction) {
var url = this._computeNavLinkURL(path, fileList, direction);
if (!url) { return; }
@@ -556,10 +598,6 @@
history.replaceState(null, null, '#' + this.$.cursor.getAddress());
},
- _handleDropdownChange: function(e) {
- e.target.blur();
- },
-
_computeDownloadLink: function(changeNum, patchRange, path) {
var url = this.changeBaseURL(changeNum, patchRange.patchNum);
url += '/patch?zip&path=' + encodeURIComponent(path);
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 e423e45..c6ccb1b 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
@@ -62,7 +62,7 @@
test('toggle left diff with a hotkey', function() {
var toggleLeftDiffStub = sandbox.stub(element.$.diff, 'toggleLeftDiff');
- MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce);
});
@@ -82,29 +82,29 @@
element.changeViewState.selectedFileIndex = 1;
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/'),
'Should navigate to /c/42/');
- MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'),
'Should navigate to /c/42/10/wheatley.md');
element._path = 'wheatley.md';
assert.equal(element.changeViewState.selectedFileIndex, 2);
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'),
'Should navigate to /c/42/10/glados.txt');
element._path = 'glados.txt';
assert.equal(element.changeViewState.selectedFileIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'),
'Should navigate to /c/42/10/chell.go');
element._path = 'chell.go';
assert.equal(element.changeViewState.selectedFileIndex, 0);
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/'),
'Should navigate to /c/42/');
assert.equal(element.changeViewState.selectedFileIndex, 0);
@@ -112,33 +112,33 @@
var showPrefsStub = sandbox.stub(element.$.prefsOverlay, 'open',
function() { return Promise.resolve({}); });
- MockInteractions.pressAndReleaseKeyOn(element, 188); // ','
+ MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
assert(showPrefsStub.calledOnce);
var scrollStub = sandbox.stub(element.$.cursor, 'moveToNextChunk');
- MockInteractions.pressAndReleaseKeyOn(element, 78); // 'n'
+ MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor, 'moveToPreviousChunk');
- MockInteractions.pressAndReleaseKeyOn(element, 80); // 'p'
+ MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p');
assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor, 'moveToNextCommentThread');
- MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']); // 'N'
+ MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor,
'moveToPreviousCommentThread');
- MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']); // 'P'
+ MockInteractions.pressAndReleaseKeyOn(element, 80, 'shift', 'p');
assert(scrollStub.calledOnce);
var computeContainerClassStub = sandbox.stub(element.$.diff,
'_computeContainerClass');
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert(computeContainerClassStub.lastCall.calledWithExactly(
false, 'SIDE_BY_SIDE', true));
- MockInteractions.pressAndReleaseKeyOn(element, 27); // 'escape'
+ MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'esc');
assert(computeContainerClassStub.lastCall.calledWithExactly(
false, 'SIDE_BY_SIDE', false));
});
@@ -175,39 +175,39 @@
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog'));
element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10');
- MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'),
'Should navigate to /c/42/5..10/wheatley.md');
element._path = 'wheatley.md';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'),
'Should navigate to /c/42/5..10/glados.txt');
element._path = 'glados.txt';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'),
'Should navigate to /c/42/5..10/chell.go');
element._path = 'chell.go';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10');
});
@@ -229,39 +229,39 @@
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog'));
element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
- MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
'Should navigate to /c/42/1/wheatley.md');
element._path = 'wheatley.md';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
'Should navigate to /c/42/1/glados.txt');
element._path = 'glados.txt';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
'Should navigate to /c/42/1/chell.go');
element._path = 'chell.go';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
});
@@ -278,39 +278,39 @@
var showStub = sandbox.stub(page, 'show');
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog'));
element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
- MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
'Should navigate to /c/42/1/wheatley.md');
element._path = 'wheatley.md';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
'Should navigate to /c/42/1/glados.txt');
element._path = 'glados.txt';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
'Should navigate to /c/42/1/chell.go');
element._path = 'chell.go';
- MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1');
});
@@ -457,7 +457,6 @@
test('diff mode selector correctly toggles the diff', function() {
var select = element.$.modeSelect;
var diffDisplay = element.$.diff;
- var blurSpy = sandbox.spy(select, 'blur');
element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
// The mode selected in the view state reflects the selected option.
@@ -477,7 +476,6 @@
assert.equal(element._getDiffViewMode(), newMode);
assert.equal(element._getDiffViewMode(), select.value);
assert.equal(element._getDiffViewMode(), diffDisplay.viewMode);
- assert(blurSpy.called, 'select should be blurred after selection');
});
test('diff mode selector initializes from preferences', function() {
@@ -557,14 +555,6 @@
assert.equal(element.$.cursor.side, 'left');
});
- test('_checkForModifiers', function() {
- assert.isTrue(element._checkForModifiers({altKey: true}));
- assert.isTrue(element._checkForModifiers({ctrlKey: true}));
- assert.isTrue(element._checkForModifiers({metaKey: true}));
- assert.isTrue(element._checkForModifiers({shiftKey: true}));
- assert.isFalse(element._checkForModifiers({}));
- });
-
test('_shortenPath with long path should add ellipsis', function() {
var path =
'level1/level2/level3/level4/file.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index 568b5e0..c610073 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -51,6 +51,10 @@
'mousedown': '_handleMouseDown', // See https://crbug.com/gerrit/4767
},
+ keyBindings: {
+ 'c': '_handleCKey',
+ },
+
placeAbove: function(el) {
var rect = this._getTargetBoundingRect(el);
var boxRect = this.getBoundingClientRect();
@@ -74,17 +78,11 @@
return rect;
},
- _checkForModifiers: function(e) {
- return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
- },
-
- _handleKey: function(e) {
+ _handleCKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- if (e.keyCode === 67) { // 'c'
- if (this._checkForModifiers(e)) { return; }
- e.preventDefault();
- this._fireCreateComment();
- }
+
+ e.preventDefault();
+ this._fireCreateComment();
},
_handleMouseDown: function(e) {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index c12966d..79ff2a5 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -49,12 +49,12 @@
});
test('ignores regular keys', function() {
- MockInteractions.pressAndReleaseKeyOn(document.body, 27); // 'esc'
+ MockInteractions.pressAndReleaseKeyOn(document.body, 27, null, 'esc');
assert.isFalse(element.fire.called);
});
test('reacts to hotkey', function() {
- MockInteractions.pressAndReleaseKeyOn(document.body, 67); // 'c'
+ MockInteractions.pressAndReleaseKeyOn(document.body, 67, null, 'c');
assert.isTrue(element.fire.called);
});
@@ -68,7 +68,7 @@
};
element.side = 'left';
element.range = range;
- MockInteractions.pressAndReleaseKeyOn(document.body, 67); // 'c'
+ MockInteractions.pressAndReleaseKeyOn(document.body, 67, null, 'c');
assert(element.fire.calledWithExactly(
'create-comment',
{
@@ -117,13 +117,5 @@
document.createRange.restore();
});
});
-
- test('_checkForModifiers', function() {
- assert.isTrue(element._checkForModifiers({altKey: true}));
- assert.isTrue(element._checkForModifiers({ctrlKey: true}));
- assert.isTrue(element._checkForModifiers({metaKey: true}));
- assert.isTrue(element._checkForModifiers({shiftKey: true}));
- assert.isFalse(element._checkForModifiers({}));
- });
});
</script>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index b758ff0..1ef6c2d 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -62,6 +62,10 @@
Gerrit.KeyboardShortcutBehavior,
],
+ keyBindings: {
+ '?': '_showKeyboardShortcuts',
+ },
+
attached: function() {
this.$.restAPI.getAccount().then(function(account) {
this._account = account;
@@ -194,12 +198,9 @@
}
},
- _handleKey: function(e) {
+ _showKeyboardShortcuts(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
-
- if (e.keyCode === 191 && e.shiftKey) { // '/' or '?' with shift key.
- this.$.keyboardShortcuts.open();
- }
+ this.$.keyboardShortcuts.open();
},
_handleKeyboardShortcutDialogClose: function() {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index 864114f..5de54bb 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -14,6 +14,7 @@
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 1c9e010..0626288 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -140,6 +140,10 @@
this.$.input.focus();
},
+ selectAll: function() {
+ this.$.input.setSelectionRange(0, this.$.input.value.length);
+ },
+
clear: function() {
this.text = '';
},
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 03fa8e43..394b2c6 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -94,7 +94,7 @@
var cancelHandler = sinon.spy();
element.addEventListener('cancel', cancelHandler);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 27); // Esc
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 27, null, 'esc');
assert.isTrue(cancelHandler.called);
assert.isTrue(element.$.suggestions.hasAttribute('hidden'));
@@ -128,19 +128,22 @@
assert.equal(element.$.cursor.index, 0);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 40); // Down
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 40, null,
+ 'down');
assert.equal(element.$.cursor.index, 1);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 40); // Down
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 40, null,
+ 'down');
assert.equal(element.$.cursor.index, 2);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 38); // Up
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 38, null, 'up');
assert.equal(element.$.cursor.index, 1);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
+ 'enter');
assert.equal(element.value, 1);
assert.isTrue(commitHandler.called);
@@ -163,7 +166,8 @@
var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
+ 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, 'suggestion');
@@ -184,7 +188,8 @@
var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
+ 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, '');
@@ -234,7 +239,8 @@
var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
+ 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, 'blah 0');
@@ -245,10 +251,10 @@
test('tab key completes only when suggestions exist', function() {
var commitStub = sinon.stub(element, '_commit');
element._suggestions = [];
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isFalse(commitStub.called);
element._suggestions = ['tunnel snakes rule!'];
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isTrue(commitStub.called);
commitStub.restore();
});
@@ -258,11 +264,11 @@
element.addEventListener('commit', commitHandler);
element._suggestions = ['tunnel snakes rule!'];
element.tabCompleteWithoutCommit = true;
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isFalse(commitHandler.called);
element.tabCompleteWithoutCommit = false;
element._suggestions = ['tunnel snakes rule!'];
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isTrue(commitHandler.called);
});
@@ -301,7 +307,7 @@
test('input-keydown event fired', function() {
var listener = sinon.spy();
element.addEventListener('input-keydown', listener);
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
flushAsynchronousOperations();
assert.isTrue(listener.called);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index e109896..01d2585 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -39,6 +39,10 @@
tabindex: '0',
},
+ keyBindings: {
+ 'space enter': '_handleCommitKey',
+ },
+
_disabledChanged: function(disabled) {
if (disabled) {
this._enabledTabindex = this.getAttribute('tabindex');
@@ -46,13 +50,9 @@
this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
},
- _handleKey: function(e) {
- switch (e.keyCode) {
- case 32: // 'spacebar'
- case 13: // 'enter'
- e.preventDefault();
- this.click();
- }
+ _handleCommitKey: function(e) {
+ e.preventDefault();
+ this.click();
},
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
index 817d8c5..9aa80b5 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
@@ -16,7 +16,6 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
-<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
<dom-module id="gr-overlay">
<template>
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
index f4a389a..9f271ed 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
@@ -24,28 +24,13 @@
Polymer.IronOverlayBehavior,
],
- detached: function() {
- Gerrit.KeyboardShortcutBehavior.enable(this._id());
- },
-
open: function() {
return new Promise(function(resolve) {
- Gerrit.KeyboardShortcutBehavior.disable(this._id());
Polymer.IronOverlayBehaviorImpl.open.apply(this, arguments);
this._awaitOpen(resolve);
}.bind(this));
},
- close: function() {
- Gerrit.KeyboardShortcutBehavior.enable(this._id());
- Polymer.IronOverlayBehaviorImpl.close.apply(this, arguments);
- },
-
- cancel: function() {
- Gerrit.KeyboardShortcutBehavior.enable(this._id());
- Polymer.IronOverlayBehaviorImpl.cancel.apply(this, arguments);
- },
-
/**
* Override the focus stops that iron-overlay-behavior tries to find.
*/
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
index f34ffcf..b5c9f0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
@@ -22,4 +22,3 @@
<dom-module id="gr-rest-api-interface">
<script src="gr-rest-api-interface.js"></script>
</dom-module>
-
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 96a8bca..07ccf2b 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
@@ -388,11 +388,13 @@
var options = this._listChangesOptionsToHex(
ListChangesOption.ALL_REVISIONS,
ListChangesOption.CHANGE_ACTIONS,
+ ListChangesOption.CURRENT_ACTIONS,
+ ListChangesOption.CURRENT_COMMIT,
ListChangesOption.DOWNLOAD_COMMANDS,
ListChangesOption.SUBMITTABLE
);
- return this._getChangeDetail(changeNum, options, opt_errFn,
- opt_cancelCondition);
+ return this._getChangeDetail(
+ changeNum, options, opt_errFn, opt_cancelCondition);
},
getDiffChangeDetail: function(changeNum, opt_errFn, opt_cancelCondition) {