Merge "Rename gr-settings-styles to gr-form-styles and update a few styles"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 20d4e45..aa27f2b 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -497,7 +497,7 @@
Deletion of references is also possible if `Push` with the force option
is granted, however that includes the permission to fast-forward and
-force-update references to exiting and new commits. Being able to push
+force-update references to existing and new commits. Being able to push
references for new commits is bad if bypassing of code review must be
prevented.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b7c50f4..75d31d2c 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5689,6 +5689,14 @@
|`destination` ||Destination branch
|`parent` |optional, defaults to 1|
Number of the parent relative to which the cherry-pick should be considered.
+|`notify` |optional|
+Notify handling that defines to whom email notifications should be sent
+after the cherry-pick. +
+Allowed values are `NONE`, `OWNER`, `OWNER_REVIEWERS` and `ALL`. +
+If not set, the default is `NONE`.
+|`notify_details` |optional|
+Additional information about whom to notify about the update as a map
+of recipient type to link:#notify-info[NotifyInfo] entity.
|===========================
[[comment-info]]
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 a4a2cbc..dd44cb9a6 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
@@ -38,12 +38,16 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.NotifyInfo;
+import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
@@ -70,6 +74,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Branch.NameKey;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.change.GetRevisionActions;
@@ -623,6 +628,47 @@
}
@Test
+ public void cherryPickNotify() throws Exception {
+ createBranch(new NameKey(project, "branch-1"));
+ createBranch(new NameKey(project, "branch-2"));
+ createBranch(new NameKey(project, "branch-3"));
+
+ // Creates a change for 'admin'.
+ PushOneCommit.Result result = createChange();
+ String changeId = project.get() + "~master~" + result.getChangeId();
+
+ // 'user' cherry-picks the change to a new branch, the source change's author/committer('admin')
+ // will be added as a reviewer of the newly created change.
+ setApiUser(user);
+ CherryPickInput input = new CherryPickInput();
+ input.message = "it goes to a new branch";
+
+ // Enable the notification. 'admin' as a reviewer should be notified.
+ input.destination = "branch-1";
+ input.notify = NotifyHandling.ALL;
+ sender.clear();
+ gApi.changes().id(changeId).current().cherryPick(input);
+ assertNotifyCc(admin);
+
+ // Disable the notification. 'admin' as a reviewer should not be notified any more.
+ input.destination = "branch-2";
+ input.notify = NotifyHandling.NONE;
+ sender.clear();
+ gApi.changes().id(changeId).current().cherryPick(input);
+ assertThat(sender.getMessages()).hasSize(0);
+
+ // Disable the notification. The user provided in the 'notifyDetails' should still be notified.
+ TestAccount userToNotify = accounts.user2();
+ input.destination = "branch-3";
+ input.notify = NotifyHandling.NONE;
+ input.notifyDetails =
+ ImmutableMap.of(RecipientType.TO, new NotifyInfo(ImmutableList.of(userToNotify.email)));
+ sender.clear();
+ gApi.changes().id(changeId).current().cherryPick(input);
+ assertNotifyTo(userToNotify);
+ }
+
+ @Test
public void canRebase() throws Exception {
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
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 1a6166e..e4b5ff5 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
@@ -33,6 +33,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
@@ -97,6 +98,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.RefSpec;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -802,6 +804,77 @@
}
}
+ @Test
+ public void submitWithCommitAndItsMergeCommitTogether() throws Exception {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+
+ RevCommit initialHead = getRemoteHead();
+
+ // Create a stable branch and bootstrap it.
+ gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
+ PushOneCommit push =
+ pushFactory.create(db, user.getIdent(), testRepo, "initial commit", "a.txt", "a");
+ PushOneCommit.Result change = push.to("refs/heads/stable");
+
+ RevCommit stable = getRemoteHead(project, "stable");
+ RevCommit master = getRemoteHead(project, "master");
+
+ assertThat(master).isEqualTo(initialHead);
+ assertThat(stable).isEqualTo(change.getCommit());
+
+ testRepo.git().fetch().call();
+ testRepo.git().branchCreate().setName("stable").setStartPoint(stable).call();
+ testRepo.git().branchCreate().setName("master").setStartPoint(master).call();
+
+ // Create a fix in stable branch.
+ testRepo.reset(stable);
+ RevCommit fix =
+ testRepo
+ .commit()
+ .parent(stable)
+ .message("small fix")
+ .add("b.txt", "b")
+ .insertChangeId()
+ .create();
+ testRepo.branch("refs/heads/stable").update(fix);
+ testRepo
+ .git()
+ .push()
+ .setRefSpecs(new RefSpec("refs/heads/stable:refs/for/stable/" + name("topic")))
+ .call();
+
+ // Merge the fix into master.
+ testRepo.reset(master);
+ RevCommit merge =
+ testRepo
+ .commit()
+ .parent(master)
+ .parent(fix)
+ .message("Merge stable into master")
+ .insertChangeId()
+ .create();
+ testRepo.branch("refs/heads/master").update(merge);
+ testRepo
+ .git()
+ .push()
+ .setRefSpecs(new RefSpec("refs/heads/master:refs/for/master/" + name("topic")))
+ .call();
+
+ // Submit together.
+ String fixId = GitUtil.getChangeId(testRepo, fix).get();
+ String mergeId = GitUtil.getChangeId(testRepo, merge).get();
+ approve(fixId);
+ approve(mergeId);
+ submit(mergeId);
+ assertMerged(fixId);
+ assertMerged(mergeId);
+ testRepo.git().fetch().call();
+ RevWalk rw = testRepo.getRevWalk();
+ master = rw.parseCommit(getRemoteHead(project, "master"));
+ assertThat(rw.isMergedInto(merge, master)).isTrue();
+ assertThat(rw.isMergedInto(fix, master)).isTrue();
+ }
+
private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Exception {
for (PushOneCommit.Result change : changes) {
try (BatchUpdate bu =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index edf5420..fa1b95f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -18,11 +18,9 @@
import static com.google.common.truth.TruthJUnit.assume;
import com.google.common.collect.Iterables;
-import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.extensions.api.changes.SubmitInput;
-import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -34,7 +32,6 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
public abstract class AbstractSubmitByMerge extends AbstractSubmit {
@@ -181,74 +178,4 @@
}
}
- @Test
- public void submitWithCommitAndItsMergeCommitTogether() throws Exception {
- assume().that(isSubmitWholeTopicEnabled()).isTrue();
-
- RevCommit initialHead = getRemoteHead();
-
- // Create a stable branch and bootstrap it.
- gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
- PushOneCommit push =
- pushFactory.create(db, user.getIdent(), testRepo, "initial commit", "a.txt", "a");
- PushOneCommit.Result change = push.to("refs/heads/stable");
-
- RevCommit stable = getRemoteHead(project, "stable");
- RevCommit master = getRemoteHead(project, "master");
-
- assertThat(master).isEqualTo(initialHead);
- assertThat(stable).isEqualTo(change.getCommit());
-
- testRepo.git().fetch().call();
- testRepo.git().branchCreate().setName("stable").setStartPoint(stable).call();
- testRepo.git().branchCreate().setName("master").setStartPoint(master).call();
-
- // Create a fix in stable branch.
- testRepo.reset(stable);
- RevCommit fix =
- testRepo
- .commit()
- .parent(stable)
- .message("small fix")
- .add("b.txt", "b")
- .insertChangeId()
- .create();
- testRepo.branch("refs/heads/stable").update(fix);
- testRepo
- .git()
- .push()
- .setRefSpecs(new RefSpec("refs/heads/stable:refs/for/stable/" + name("topic")))
- .call();
-
- // Merge the fix into master.
- testRepo.reset(master);
- RevCommit merge =
- testRepo
- .commit()
- .parent(master)
- .parent(fix)
- .message("Merge stable into master")
- .insertChangeId()
- .create();
- testRepo.branch("refs/heads/master").update(merge);
- testRepo
- .git()
- .push()
- .setRefSpecs(new RefSpec("refs/heads/master:refs/for/master/" + name("topic")))
- .call();
-
- // Submit together.
- String fixId = GitUtil.getChangeId(testRepo, fix).get();
- String mergeId = GitUtil.getChangeId(testRepo, merge).get();
- approve(fixId);
- approve(mergeId);
- submit(mergeId);
- assertMerged(fixId);
- assertMerged(mergeId);
- testRepo.git().fetch().call();
- RevWalk rw = testRepo.getRevWalk();
- master = rw.parseCommit(getRemoteHead(project, "master"));
- assertThat(rw.isMergedInto(merge, master)).isTrue();
- assertThat(rw.isMergedInto(fix, master)).isTrue();
- }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 16dbee3..383858d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -57,13 +57,16 @@
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
+import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -777,79 +780,140 @@
@Test
public void deleteCommentByRewritingCommitHistory() throws Exception {
- // Create change (the 1st commit on the change's meta branch).
- PushOneCommit.Result result = createChange();
- String changeId = result.getChangeId();
- Change.Id id = result.getChange().getId();
+ // Creates the following commit history on the meta branch of the test change. Then tries to
+ // delete the comments one by one, which will rewrite most of the commits on the 'meta' branch.
+ // Commits will be rewritten N times for N added comments. After each deletion, the meta branch
+ // should keep its previous state except that the target comment's message should be updated.
- // Add two comments in patch set 1 (the 2nd commit on the change's meta branch).
- ReviewInput reviewInput = new ReviewInput();
- CommentInput comment1 = newComment(FILE_NAME, Side.REVISION, 0, "My password: abc123", false);
- CommentInput comment2 = newComment(FILE_NAME, Side.REVISION, 1, "nit: long line", false);
- reviewInput.comments = ImmutableMap.of(FILE_NAME, Lists.newArrayList(comment1, comment2));
- reviewInput.label("Code-Review", 1);
- gApi.changes().id(changeId).current().review(reviewInput);
+ // 1st commit: Create PS1.
+ PushOneCommit.Result result1 = createChange(SUBJECT, "a.txt", "a");
+ Change.Id id = result1.getChange().getId();
+ String changeId = result1.getChangeId();
+ String ps1 = result1.getCommit().name();
- // Create patch set 2 (the 3rd commit on the change's meta branch).
- amendChange(changeId);
+ // 2nd commit: Add (c1) to PS1.
+ CommentInput c1 = newComment("a.txt", "comment 1");
+ addComments(changeId, ps1, c1);
- // Add 'comment3' in patch set 2 (the 4th commit on the change's meta branch).
- CommentInput comment3 = addComment(changeId, "typo");
+ // 3rd commit: Add (c2, c3) to PS1.
+ CommentInput c2 = newComment("a.txt", "comment 2");
+ CommentInput c3 = newComment("a.txt", "comment 3");
+ addComments(changeId, ps1, c2, c3);
- Map<String, List<CommentInfo>> commentsMap = getPublishedComments(changeId);
- assertThat(commentsMap).hasSize(1);
- assertThat(commentsMap.get(FILE_NAME)).hasSize(3);
- Optional<CommentInfo> targetCommentInfo =
- commentsMap
- .get(FILE_NAME)
- .stream()
- .filter(c -> c.message.equals("My password: abc123"))
- .findFirst();
- assertThat(targetCommentInfo.isPresent()).isTrue();
+ // 4th commit: Add (c4) to PS1.
+ CommentInput c4 = newComment("a.txt", "comment 4");
+ addComments(changeId, ps1, c4);
- List<RevCommit> commitsBeforeDelete = new ArrayList<>();
- if (notesMigration.commitChangeWrites()) {
- commitsBeforeDelete = getCommits(id);
- }
+ // 5th commit: Create PS2.
+ PushOneCommit.Result result2 = amendChange(changeId, "refs/for/master", "b.txt", "b");
+ String ps2 = result2.getCommit().name();
- String uuid = targetCommentInfo.get().id;
- // Get the target comment.
- CommentInfo oldComment =
- gApi.changes().id(changeId).revision(result.getCommit().getName()).comment(uuid).get();
+ // 6th commit: Add (c5) to PS1.
+ CommentInput c5 = newComment("a.txt", "comment 5");
+ addComments(changeId, ps1, c5);
- // Delete the target comment.
- DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
+ // 7th commit: Add (c6) to PS2.
+ CommentInput c6 = newComment("b.txt", "comment 6");
+ addComments(changeId, ps2, c6);
+
+ // 8th commit: Create PS3.
+ PushOneCommit.Result result3 = amendChange(changeId);
+ String ps3 = result3.getCommit().name();
+
+ // 9th commit: Create PS4.
+ PushOneCommit.Result result4 = amendChange(changeId, "refs/for/master", "c.txt", "c");
+ String ps4 = result4.getCommit().name();
+
+ // 10th commit: Add (c7, c8) to PS4.
+ CommentInput c7 = newComment("c.txt", "comment 7");
+ CommentInput c8 = newComment("b.txt", "comment 8");
+ addComments(changeId, ps4, c7, c8);
+
+ // 11th commit: Add (c9) to PS2.
+ CommentInput c9 = newComment("b.txt", "comment 9");
+ addComments(changeId, ps2, c9);
+
+ List<CommentInfo> commentsBeforeDelete = getChangeSortedComments(changeId);
+ assertThat(commentsBeforeDelete).hasSize(9);
+ // PS1 has comments [c1, c2, c3, c4, c5].
+ assertThat(getRevisionComments(changeId, ps1)).hasSize(5);
+ // PS2 has comments [c6, c9].
+ assertThat(getRevisionComments(changeId, ps2)).hasSize(2);
+ // PS3 has no comment.
+ assertThat(getRevisionComments(changeId, ps3)).hasSize(0);
+ // PS4 has comments [c7, c8].
+ assertThat(getRevisionComments(changeId, ps4)).hasSize(2);
+
setApiUser(admin);
- CommentInfo updatedComment =
- gApi.changes()
- .id(changeId)
- .revision(result.getCommit().getName())
- .comment(uuid)
- .delete(input);
+ for (int i = 0; i < commentsBeforeDelete.size(); i++) {
+ List<RevCommit> commitsBeforeDelete = new ArrayList<>();
+ if (notesMigration.commitChangeWrites()) {
+ commitsBeforeDelete = getCommits(id);
+ }
- String expectedMsg =
- String.format("Comment removed by: %s; Reason: %s", admin.fullName, input.reason);
+ CommentInfo comment = commentsBeforeDelete.get(i);
+ String uuid = comment.id;
+ int patchSet = comment.patchSet;
+ // 'oldComment' has some fields unset compared with 'comment'.
+ CommentInfo oldComment = gApi.changes().id(changeId).revision(patchSet).comment(uuid).get();
- assertThat(updatedComment.message).isEqualTo(expectedMsg);
- updatedComment.message = oldComment.message;
- assertThat(updatedComment).isEqualTo(oldComment);
+ DeleteCommentInput input = new DeleteCommentInput("delete comment " + uuid);
+ CommentInfo updatedComment =
+ gApi.changes().id(changeId).revision(patchSet).comment(uuid).delete(input);
- // Check the comment's message has been replaced in NoteDb.
- if (notesMigration.commitChangeWrites()) {
- assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg);
+ String expectedMsg =
+ String.format("Comment removed by: %s; Reason: %s", admin.fullName, input.reason);
+ assertThat(updatedComment.message).isEqualTo(expectedMsg);
+ oldComment.message = expectedMsg;
+ assertThat(updatedComment).isEqualTo(oldComment);
+
+ // Check the NoteDb state after the deletion.
+ if (notesMigration.commitChangeWrites()) {
+ assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg);
+ }
+
+ comment.message = expectedMsg;
+ commentsBeforeDelete.set(i, comment);
+ List<CommentInfo> commentsAfterDelete = getChangeSortedComments(changeId);
+ assertThat(commentsAfterDelete).isEqualTo(commentsBeforeDelete);
}
// Make sure that comments can still be added correctly.
- CommentInput comment4 = addComment(changeId, "too much space");
- commentsMap = getPublishedComments(changeId);
+ CommentInput c10 = newComment("a.txt", "comment 10");
+ CommentInput c11 = newComment("b.txt", "comment 11");
+ CommentInput c12 = newComment("a.txt", "comment 12");
+ CommentInput c13 = newComment("c.txt", "comment 13");
+ addComments(changeId, ps1, c10);
+ addComments(changeId, ps2, c11);
+ addComments(changeId, ps3, c12);
+ addComments(changeId, ps4, c13);
- assertThat(commentsMap).hasSize(1);
- List<CommentInput> comments =
- Lists.transform(commentsMap.get(FILE_NAME), infoToInput(FILE_NAME));
+ assertThat(getChangeSortedComments(changeId)).hasSize(13);
+ assertThat(getRevisionComments(changeId, ps1)).hasSize(6);
+ assertThat(getRevisionComments(changeId, ps2)).hasSize(3);
+ assertThat(getRevisionComments(changeId, ps3)).hasSize(1);
+ assertThat(getRevisionComments(changeId, ps4)).hasSize(3);
+ }
- // Change comment1's message to the expected message.
- comment1.message = expectedMsg;
- assertThat(comments).containsExactly(comment1, comment2, comment3, comment4);
+ private List<CommentInfo> getChangeSortedComments(String changeId) throws Exception {
+ List<CommentInfo> comments = new ArrayList<>();
+ Map<String, List<CommentInfo>> commentsMap = getPublishedComments(changeId);
+ for (Entry<String, List<CommentInfo>> e : commentsMap.entrySet()) {
+ for (CommentInfo c : e.getValue()) {
+ c.path = e.getKey(); // Set the comment's path field.
+ comments.add(c);
+ }
+ }
+ comments.sort(Comparator.comparing(c -> c.id));
+ return comments;
+ }
+
+ private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception {
+ return getPublishedComments(changeId, revId)
+ .values()
+ .stream()
+ .flatMap(List::stream)
+ .collect(Collectors.toList());
}
private CommentInput addComment(String changeId, String message) throws Exception {
@@ -860,6 +924,13 @@
return comment;
}
+ private void addComments(String changeId, String revision, CommentInput... commentInputs)
+ throws Exception {
+ ReviewInput input = new ReviewInput();
+ input.comments = Arrays.stream(commentInputs).collect(Collectors.groupingBy(c -> c.path));
+ gApi.changes().id(changeId).revision(revision).review(input);
+ }
+
private List<RevCommit> getCommits(Change.Id changeId) throws IOException {
try (Repository repo = repoManager.openRepository(project);
RevWalk revWalk = new RevWalk(repo)) {
@@ -989,6 +1060,10 @@
return gApi.changes().id(changeId).revision(revId).draft(uuid).get();
}
+ private static CommentInput newComment(String file, String message) {
+ return newComment(file, Side.REVISION, 0, message, false);
+ }
+
private static CommentInput newComment(
String path, Side side, int line, String message, Boolean unresolved) {
CommentInput c = new CommentInput();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
index 2e1bb13..3ac3601 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
@@ -14,8 +14,13 @@
package com.google.gerrit.extensions.api.changes;
+import java.util.Map;
+
public class CherryPickInput {
public String message;
public String destination;
public Integer parent;
+
+ public NotifyHandling notify = NotifyHandling.NONE;
+ public Map<RecipientType, NotifyInfo> notifyDetails;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index 1a1f8cc..35aa4ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -66,7 +66,7 @@
BatchUpdate.Factory updateFactory, RevisionResource revision, CherryPickInput input)
throws OrmException, IOException, UpdateException, RestApiException {
final ChangeControl control = revision.getControl();
- int parent = input.parent == null ? 1 : input.parent;
+ input.parent = input.parent == null ? 1 : input.parent;
if (input.message == null || input.message.trim().isEmpty()) {
throw new BadRequestException("message must be non-empty");
@@ -100,10 +100,9 @@
updateFactory,
revision.getChange(),
revision.getPatchSet(),
- input.message,
+ input,
refName,
- refControl,
- parent);
+ refControl);
return json.noOptions().format(revision.getProject(), cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index a540298..7c0a7be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -18,7 +18,8 @@
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Branch;
@@ -80,6 +81,7 @@
private final MergeUtil.Factory mergeUtilFactory;
private final ChangeMessagesUtil changeMessagesUtil;
private final PatchSetUtil psUtil;
+ private final NotifyUtil notifyUtil;
@Inject
CherryPickChange(
@@ -93,7 +95,8 @@
PatchSetInserter.Factory patchSetInserterFactory,
MergeUtil.Factory mergeUtilFactory,
ChangeMessagesUtil changeMessagesUtil,
- PatchSetUtil psUtil) {
+ PatchSetUtil psUtil,
+ NotifyUtil notifyUtil) {
this.db = db;
this.seq = seq;
this.queryProvider = queryProvider;
@@ -105,16 +108,16 @@
this.mergeUtilFactory = mergeUtilFactory;
this.changeMessagesUtil = changeMessagesUtil;
this.psUtil = psUtil;
+ this.notifyUtil = notifyUtil;
}
public Change.Id cherryPick(
BatchUpdate.Factory batchUpdateFactory,
Change change,
PatchSet patch,
- String message,
+ CherryPickInput input,
String ref,
- RefControl refControl,
- int parent)
+ RefControl refControl)
throws OrmException, IOException, InvalidChangeOperationException, IntegrationException,
UpdateException, RestApiException {
return cherryPick(
@@ -125,10 +128,9 @@
change.getTopic(),
change.getProject(),
ObjectId.fromString(patch.getRevision().get()),
- message,
+ input,
ref,
- refControl,
- parent);
+ refControl);
}
public Change.Id cherryPick(
@@ -139,10 +141,9 @@
@Nullable String sourceChangeTopic,
Project.NameKey project,
ObjectId sourceCommit,
- String message,
+ CherryPickInput input,
String targetRef,
- RefControl targetRefControl,
- int parent)
+ RefControl targetRefControl)
throws OrmException, IOException, InvalidChangeOperationException, IntegrationException,
UpdateException, RestApiException {
@@ -170,12 +171,12 @@
CodeReviewCommit commitToCherryPick = revWalk.parseCommit(sourceCommit);
- if (parent <= 0 || parent > commitToCherryPick.getParentCount()) {
+ if (input.parent <= 0 || input.parent > commitToCherryPick.getParentCount()) {
throw new InvalidChangeOperationException(
String.format(
"Cherry Pick: Parent %s does not exist. Please specify a parent in"
+ " range [1, %s].",
- parent, commitToCherryPick.getParentCount()));
+ input.parent, commitToCherryPick.getParentCount()));
}
Timestamp now = TimeUtil.nowTs();
@@ -187,8 +188,8 @@
mergeTip,
commitToCherryPick.getAuthorIdent(),
committerIdent,
- message);
- String commitMessage = ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
+ input.message);
+ String commitMessage = ChangeIdUtil.insertId(input.message, computedChangeId).trim() + '\n';
CodeReviewCommit cherryPickCommit;
try {
@@ -204,7 +205,7 @@
committerIdent,
commitMessage,
revWalk,
- parent - 1,
+ input.parent - 1,
false);
Change.Key changeKey;
@@ -234,7 +235,7 @@
// will be added as a new patch set.
ChangeControl destCtl =
targetRefControl.getProjectControl().controlFor(destChanges.get(0).notes());
- result = insertPatchSet(bu, git, destCtl, cherryPickCommit);
+ result = insertPatchSet(bu, git, destCtl, cherryPickCommit, input);
} else {
// Change key not found on destination branch. We can create a new
// change.
@@ -249,7 +250,8 @@
targetRefControl.getRefName(),
newTopic,
sourceBranch,
- sourceCommit);
+ sourceCommit,
+ input);
if (sourceChangeId != null && sourcePatchId != null) {
bu.addOp(
@@ -268,20 +270,23 @@
}
private Change.Id insertPatchSet(
- BatchUpdate bu, Repository git, ChangeControl destCtl, CodeReviewCommit cherryPickCommit)
- throws IOException, OrmException {
+ BatchUpdate bu,
+ Repository git,
+ ChangeControl destCtl,
+ CodeReviewCommit cherryPickCommit,
+ CherryPickInput input)
+ throws IOException, OrmException, BadRequestException {
Change destChange = destCtl.getChange();
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
- PatchSetInserter inserter = patchSetInserterFactory.create(destCtl, psId, cherryPickCommit);
- PatchSet.Id newPatchSetId = inserter.getPatchSetId();
PatchSet current = psUtil.current(db.get(), destCtl.getNotes());
- bu.addOp(
- destChange.getId(),
- inserter
- .setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
- .setDraft(current.isDraft())
- .setNotify(NotifyHandling.NONE));
+ PatchSetInserter inserter = patchSetInserterFactory.create(destCtl, psId, cherryPickCommit);
+ inserter
+ .setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".")
+ .setDraft(current.isDraft())
+ .setNotify(input.notify)
+ .setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
+ bu.addOp(destChange.getId(), inserter);
return destChange.getId();
}
@@ -291,12 +296,15 @@
String refName,
String topic,
Branch.NameKey sourceBranch,
- ObjectId sourceCommit)
- throws OrmException, IOException {
+ ObjectId sourceCommit,
+ CherryPickInput input)
+ throws OrmException, IOException, BadRequestException {
Change.Id changeId = new Change.Id(seq.nextChangeId());
ChangeInserter ins =
changeInserterFactory.create(changeId, cherryPickCommit, refName).setTopic(topic);
- ins.setMessage(messageForDestinationChange(ins.getPatchSetId(), sourceBranch, sourceCommit));
+ ins.setMessage(messageForDestinationChange(ins.getPatchSetId(), sourceBranch, sourceCommit))
+ .setNotify(input.notify)
+ .setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
bu.insertChange(ins);
return changeId;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java
index 4c027dd..b44a8b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java
@@ -59,9 +59,11 @@
public ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, CommitResource rsrc, CherryPickInput input)
throws OrmException, IOException, UpdateException, RestApiException {
+ RevCommit commit = rsrc.getCommit();
String message = Strings.nullToEmpty(input.message).trim();
+ input.message = message.isEmpty() ? commit.getFullMessage() : message;
String destination = Strings.nullToEmpty(input.destination).trim();
- int parent = input.parent == null ? 1 : input.parent;
+ input.parent = input.parent == null ? 1 : input.parent;
if (destination.isEmpty()) {
throw new BadRequestException("destination must be non-empty");
@@ -73,7 +75,6 @@
throw new AuthException(capable.getMessage());
}
- RevCommit commit = rsrc.getCommit();
String refName = RefNames.fullName(destination);
RefControl refControl = projectControl.controlForRef(refName);
if (!refControl.canUpload()) {
@@ -84,17 +85,7 @@
try {
Change.Id cherryPickedChangeId =
cherryPickChange.cherryPick(
- updateFactory,
- null,
- null,
- null,
- null,
- project,
- commit,
- message.isEmpty() ? commit.getFullMessage() : message,
- refName,
- refControl,
- parent);
+ updateFactory, null, null, null, null, project, commit, input, refName, refControl);
return json.noOptions().format(project, cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
index 78fc495..733bf49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
@@ -29,20 +29,20 @@
private final CodeReviewRevWalk rw;
private final RevFlag canMergeFlag;
private final Set<RevCommit> accepted;
+ private final Set<CodeReviewCommit> incoming;
- public MergeSorter(CodeReviewRevWalk rw, Set<RevCommit> alreadyAccepted, RevFlag canMergeFlag) {
+ public MergeSorter(
+ CodeReviewRevWalk rw,
+ Set<RevCommit> alreadyAccepted,
+ RevFlag canMergeFlag,
+ Set<CodeReviewCommit> incoming) {
this.rw = rw;
this.canMergeFlag = canMergeFlag;
this.accepted = alreadyAccepted;
+ this.incoming = incoming;
}
Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> toMerge) throws IOException {
- return sort(toMerge, toMerge);
- }
-
- Collection<CodeReviewCommit> sort(
- final Collection<CodeReviewCommit> toMerge, final Collection<CodeReviewCommit> incoming)
- throws IOException {
final Set<CodeReviewCommit> heads = new HashSet<>();
final Set<CodeReviewCommit> sort = new HashSet<>(toMerge);
while (!sort.isEmpty()) {
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 6446fdd..11e3051 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
@@ -209,11 +209,10 @@
}
public List<CodeReviewCommit> reduceToMinimalMerge(
- MergeSorter mergeSorter, Collection<CodeReviewCommit> toSort, Set<CodeReviewCommit> incoming)
- throws IntegrationException {
+ MergeSorter mergeSorter, Collection<CodeReviewCommit> toSort) throws IntegrationException {
List<CodeReviewCommit> result = new ArrayList<>();
try {
- result.addAll(mergeSorter.sort(toSort, incoming));
+ result.addAll(mergeSorter.sort(toSort));
} catch (IOException e) {
throw new IntegrationException("Branch head sorting failed", e);
}
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 94e78f8..dbfb19c 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
@@ -42,23 +42,26 @@
private final RevCommit initialTip;
private final Set<RevCommit> alreadyAccepted;
private final InternalChangeQuery internalChangeQuery;
+ private final Set<CodeReviewCommit> incoming;
public RebaseSorter(
CodeReviewRevWalk rw,
RevCommit initialTip,
Set<RevCommit> alreadyAccepted,
RevFlag canMergeFlag,
- InternalChangeQuery internalChangeQuery) {
+ InternalChangeQuery internalChangeQuery,
+ Set<CodeReviewCommit> incoming) {
this.rw = rw;
this.canMergeFlag = canMergeFlag;
this.initialTip = initialTip;
this.alreadyAccepted = alreadyAccepted;
this.internalChangeQuery = internalChangeQuery;
+ this.incoming = incoming;
}
- public List<CodeReviewCommit> sort(Collection<CodeReviewCommit> incoming) throws IOException {
+ public List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort) throws IOException {
final List<CodeReviewCommit> sorted = new ArrayList<>();
- final Set<CodeReviewCommit> sort = new HashSet<>(incoming);
+ final Set<CodeReviewCommit> sort = new HashSet<>(toSort);
while (!sort.isEmpty()) {
final CodeReviewCommit n = removeOne(sort);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
index 7151486..38a193d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
@@ -29,8 +29,7 @@
@Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge)
throws IntegrationException {
- List<CodeReviewCommit> sorted =
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge, args.incoming);
+ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
CodeReviewCommit newTipCommit =
args.mergeUtil.getFirstFastForward(args.mergeTip.getInitialTip(), args.rw, sorted);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index ce045f8..1664be4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -28,8 +28,7 @@
@Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge)
throws IntegrationException {
- List<CodeReviewCommit> sorted =
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge, args.incoming);
+ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
if (args.mergeTip.getInitialTip() == null && !sorted.isEmpty()) {
// The branch is unborn. Take a fast-forward resolution to
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index e7db1a8..d30aab2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -28,8 +28,7 @@
@Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge)
throws IntegrationException {
- List<CodeReviewCommit> sorted =
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge, args.incoming);
+ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
if (args.mergeTip.getInitialTip() == null
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 40bb6c1..d4487b5 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
@@ -28,7 +28,6 @@
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.git.MergeTip;
-import com.google.gerrit.server.git.RebaseSorter;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -57,7 +56,12 @@
@Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge)
throws IntegrationException {
- List<CodeReviewCommit> sorted = sort(toMerge);
+ List<CodeReviewCommit> sorted;
+ try {
+ sorted = args.rebaseSorter.sort(toMerge);
+ } catch (IOException e) {
+ throw new IntegrationException("Commit sorting failed", e);
+ }
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
boolean first = true;
@@ -67,7 +71,7 @@
// MERGE_IF_NECESSARY semantics to avoid creating duplicate
// commits.
//
- sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted, args.incoming);
+ sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted);
break;
}
}
@@ -286,21 +290,6 @@
args.alreadyAccepted.add(mergeTip.getCurrentTip());
}
- private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
- throws IntegrationException {
- try {
- return new RebaseSorter(
- args.rw,
- args.mergeTip.getInitialTip(),
- args.alreadyAccepted,
- args.canMergeFlag,
- args.internalChangeQuery)
- .sort(toSort);
- } catch (IOException e) {
- throw new IntegrationException("Commit sorting failed", e);
- }
- }
-
static boolean dryRun(
SubmitDryRun.Arguments args,
Repository repo,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
index da35f78..0d012e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git.strategy;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.git.CodeReviewCommit;
@@ -108,7 +109,7 @@
repo,
rw,
mergeUtilFactory.create(getProject(destBranch)),
- new MergeSorter(rw, alreadyAccepted, canMerge));
+ new MergeSorter(rw, alreadyAccepted, canMerge, ImmutableSet.of(toMergeCommit)));
switch (submitType) {
case CHERRY_PICK:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index e9ed294..95bdf33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -43,6 +43,7 @@
import com.google.gerrit.server.git.MergeSorter;
import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.RebaseSorter;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.validators.OnSubmitValidators;
@@ -126,7 +127,6 @@
final RevFlag canMergeFlag;
final ReviewDb db;
final Set<RevCommit> alreadyAccepted;
- final Set<CodeReviewCommit> incoming;
final RequestId submissionId;
final SubmitType submitType;
final NotifyHandling notifyHandling;
@@ -135,6 +135,7 @@
final ProjectState project;
final MergeSorter mergeSorter;
+ final RebaseSorter rebaseSorter;
final MergeUtil mergeUtil;
final boolean dryrun;
@@ -196,7 +197,6 @@
this.canMergeFlag = canMergeFlag;
this.db = db;
this.alreadyAccepted = alreadyAccepted;
- this.incoming = incoming;
this.submissionId = submissionId;
this.submitType = submitType;
this.notifyHandling = notifyHandling;
@@ -209,7 +209,15 @@
projectCache.get(destBranch.getParentKey()),
"project not found: %s",
destBranch.getParentKey());
- this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
+ this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag, incoming);
+ this.rebaseSorter =
+ new RebaseSorter(
+ rw,
+ mergeTip.getInitialTip(),
+ alreadyAccepted,
+ canMergeFlag,
+ internalChangeQuery,
+ incoming);
this.mergeUtil = mergeUtilFactory.create(project);
this.onSubmitValidatorsFactory = onSubmitValidatorsFactory;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/AbstractVersionManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/AbstractVersionManager.java
index 33cca1e..733fcce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/AbstractVersionManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/AbstractVersionManager.java
@@ -64,7 +64,7 @@
reindexers = Maps.newHashMapWithExpectedSize(defs.size());
onlineUpgrade = cfg.getBoolean("index", null, "onlineUpgrade", true);
runReindexMsg =
- "No index versions ready; run java -jar "
+ "No index versions for index '%s' ready; run java -jar "
+ sitePaths.gerrit_war.toAbsolutePath()
+ " reindex";
}
@@ -142,7 +142,7 @@
}
}
if (search == null) {
- throw new ProvisionException(runReindexMsg);
+ throw new ProvisionException(String.format(runReindexMsg, def.getName()));
}
IndexFactory<K, V, I> factory = def.getIndexFactory();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index 8765607..a3bd230 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -74,10 +74,12 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterLine;
/**
@@ -324,10 +326,36 @@
return SchemaUtil.getPersonParts(cd.getAuthor());
}
+ public static Set<String> getAuthorNameAndEmail(ChangeData cd) throws OrmException, IOException {
+ return getNameAndEmail(cd.getAuthor());
+ }
+
public static Set<String> getCommitterParts(ChangeData cd) throws OrmException, IOException {
return SchemaUtil.getPersonParts(cd.getCommitter());
}
+ public static Set<String> getCommitterNameAndEmail(ChangeData cd)
+ throws OrmException, IOException {
+ return getNameAndEmail(cd.getCommitter());
+ }
+
+ private static Set<String> getNameAndEmail(PersonIdent person) {
+ if (person == null) {
+ return ImmutableSet.of();
+ }
+
+ String name = person.getName().toLowerCase(Locale.US);
+ String email = person.getEmailAddress().toLowerCase(Locale.US);
+
+ StringBuilder nameEmailBuilder = new StringBuilder();
+ PersonIdent.appendSanitized(nameEmailBuilder, name);
+ nameEmailBuilder.append(" <");
+ PersonIdent.appendSanitized(nameEmailBuilder, email);
+ nameEmailBuilder.append('>');
+
+ return ImmutableSet.of(name, email, nameEmailBuilder.toString());
+ }
+
/**
* The exact email address, or any part of the author name or email address, in the current patch
* set.
@@ -335,6 +363,11 @@
public static final FieldDef<ChangeData, Iterable<String>> AUTHOR =
fullText(ChangeQueryBuilder.FIELD_AUTHOR).buildRepeatable(ChangeField::getAuthorParts);
+ /** The exact name, email address and NameEmail of the author. */
+ public static final FieldDef<ChangeData, Iterable<String>> EXACT_AUTHOR =
+ exact(ChangeQueryBuilder.FIELD_EXACTAUTHOR)
+ .buildRepeatable(ChangeField::getAuthorNameAndEmail);
+
/**
* The exact email address, or any part of the committer name or email address, in the current
* patch set.
@@ -342,6 +375,11 @@
public static final FieldDef<ChangeData, Iterable<String>> COMMITTER =
fullText(ChangeQueryBuilder.FIELD_COMMITTER).buildRepeatable(ChangeField::getCommitterParts);
+ /** The exact name, email address, and NameEmail of the committer. */
+ public static final FieldDef<ChangeData, Iterable<String>> EXACT_COMMITTER =
+ exact(ChangeQueryBuilder.FIELD_EXACTCOMMITTER)
+ .buildRepeatable(ChangeField::getCommitterNameAndEmail);
+
public static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
/** Serialized change object, used for pre-populating results. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index ec507f4..be4f24b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -72,8 +72,10 @@
@Deprecated static final Schema<ChangeData> V40 = schema(V39, ChangeField.PRIVATE);
@Deprecated static final Schema<ChangeData> V41 = schema(V40, ChangeField.REVIEWER_BY_EMAIL);
+ @Deprecated static final Schema<ChangeData> V42 = schema(V41, ChangeField.WIP);
- static final Schema<ChangeData> V42 = schema(V41, ChangeField.WIP);
+ static final Schema<ChangeData> V43 =
+ schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
index 8a43bc6..c11e6c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
@@ -95,9 +95,9 @@
ObjectReader reader = revWalk.getObjectReader();
ObjectId newTip = revWalk.next(); // The first commit will not be rewritten.
- NoteMap newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
Map<String, Comment> parentComments =
- getPublishedComments(noteUtil, changeId, reader, newTipNoteMap);
+ getPublishedComments(
+ noteUtil, changeId, reader, NoteMap.read(reader, revWalk.parseCommit(newTip)));
boolean rewrite = false;
RevCommit originalCommit;
@@ -120,13 +120,12 @@
newTip =
rewriteCommit(
originalCommit,
- newTipNoteMap,
+ NoteMap.read(reader, revWalk.parseCommit(newTip)),
newTip,
inserter,
reader,
putInComments,
deletedComments);
- newTipNoteMap = NoteMap.read(reader, revWalk.parseCommit(newTip));
parentComments = currComments;
}
@@ -229,8 +228,9 @@
byte[] data = entry.getValue().build(noteUtil, noteUtil.getWriteJson());
if (data.length == 0) {
revNotesMap.noteMap.remove(objectId);
+ } else {
+ revNotesMap.noteMap.set(objectId, inserter.insert(OBJ_BLOB, data));
}
- revNotesMap.noteMap.set(objectId, inserter.insert(OBJ_BLOB, data));
}
CommitBuilder cb = new CommitBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
index aec8442..deec7e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -61,7 +61,7 @@
MutableInteger p = new MutableInteger();
trimLeadingEmptyLines(raw, p);
if (p.value >= raw.length) {
- comments = null;
+ comments = ImmutableList.of();
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 39e9241..af0a3b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -57,6 +57,7 @@
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.SchemaUtil;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -87,7 +88,9 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
@@ -127,6 +130,7 @@
public static final String FIELD_AGE = "age";
public static final String FIELD_ASSIGNEE = "assignee";
public static final String FIELD_AUTHOR = "author";
+ public static final String FIELD_EXACTAUTHOR = "exactauthor";
public static final String FIELD_BEFORE = "before";
public static final String FIELD_CHANGE = "change";
public static final String FIELD_CHANGE_ID = "change_id";
@@ -134,6 +138,7 @@
public static final String FIELD_COMMENTBY = "commentby";
public static final String FIELD_COMMIT = "commit";
public static final String FIELD_COMMITTER = "committer";
+ public static final String FIELD_EXACTCOMMITTER = "exactcommitter";
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
@@ -1119,13 +1124,21 @@
}
@Operator
- public Predicate<ChangeData> author(String who) {
- return new AuthorPredicate(who);
+ public Predicate<ChangeData> author(String who) throws QueryParseException {
+ if (args.getSchema().hasField(ChangeField.EXACT_AUTHOR)) {
+ return getAuthorOrCommitterPredicate(
+ who.trim(), ExactAuthorPredicate::new, AuthorPredicate::new);
+ }
+ return getAuthorOrCommitterFullTextPredicate(who.trim(), AuthorPredicate::new);
}
@Operator
- public Predicate<ChangeData> committer(String who) {
- return new CommitterPredicate(who);
+ public Predicate<ChangeData> committer(String who) throws QueryParseException {
+ if (args.getSchema().hasField(ChangeField.EXACT_COMMITTER)) {
+ return getAuthorOrCommitterPredicate(
+ who.trim(), ExactCommitterPredicate::new, CommitterPredicate::new);
+ }
+ return getAuthorOrCommitterFullTextPredicate(who.trim(), CommitterPredicate::new);
}
@Operator
@@ -1199,6 +1212,30 @@
return Predicate.or(predicates);
}
+ private Predicate<ChangeData> getAuthorOrCommitterPredicate(
+ String who,
+ Function<String, Predicate<ChangeData>> exactPredicateFunc,
+ Function<String, Predicate<ChangeData>> fullPredicateFunc)
+ throws QueryParseException {
+ if (Address.tryParse(who) != null) {
+ return exactPredicateFunc.apply(who);
+ }
+ return getAuthorOrCommitterFullTextPredicate(who, fullPredicateFunc);
+ }
+
+ private Predicate<ChangeData> getAuthorOrCommitterFullTextPredicate(
+ String who, Function<String, Predicate<ChangeData>> fullPredicateFunc)
+ throws QueryParseException {
+ Set<String> parts = SchemaUtil.getNameParts(who);
+ if (parts.isEmpty()) {
+ throw error("invalid value");
+ }
+
+ List<Predicate<ChangeData>> predicates =
+ parts.stream().map(fullPredicateFunc).collect(Collectors.toList());
+ return Predicate.and(predicates);
+ }
+
private Set<Account.Id> parseAccount(String who) throws QueryParseException, OrmException {
if (isSelf(who)) {
return Collections.singleton(self());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactAuthorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactAuthorPredicate.java
new file mode 100644
index 0000000..bca5d3b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactAuthorPredicate.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.gerrit.server.index.change.ChangeField.EXACT_AUTHOR;
+import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_EXACTAUTHOR;
+
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import java.util.Locale;
+
+public class ExactAuthorPredicate extends ChangeIndexPredicate {
+ public ExactAuthorPredicate(String value) {
+ super(EXACT_AUTHOR, FIELD_EXACTAUTHOR, value.toLowerCase(Locale.US));
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ try {
+ return ChangeField.getAuthorNameAndEmail(object).contains(getValue());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitterPredicate.java
new file mode 100644
index 0000000..3fae5e5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitterPredicate.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.gerrit.server.index.change.ChangeField.EXACT_COMMITTER;
+import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_EXACTCOMMITTER;
+
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import java.util.Locale;
+
+public class ExactCommitterPredicate extends ChangeIndexPredicate {
+ public ExactCommitterPredicate(String value) {
+ super(EXACT_COMMITTER, FIELD_EXACTCOMMITTER, value.toLowerCase(Locale.US));
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ try {
+ return ChangeField.getCommitterNameAndEmail(object).contains(getValue());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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 34536ab..ba37a5e 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
@@ -124,6 +124,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -493,57 +494,82 @@
}
@Test
- public void byAuthor() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
-
- // By exact email address
- assertQuery("author:jauthor@example.com", change1);
-
- // By email address part
- assertQuery("author:jauthor", change1);
- assertQuery("author:example", change1);
- assertQuery("author:example.com", change1);
-
- // By name part
- assertQuery("author:Author", change1);
-
- // Case insensitive
- assertQuery("author:jAuThOr", change1);
- assertQuery("author:ExAmPlE", change1);
-
- // By non-existing email address / name / part
- assertQuery("author:jcommitter@example.com");
- assertQuery("author:somewhere.com");
- assertQuery("author:jcommitter");
- assertQuery("author:Committer");
+ public void byAuthorExact() throws Exception {
+ byAuthorOrCommitterExact("author:");
}
@Test
- public void byCommitter() throws Exception {
+ public void byAuthorFullText() throws Exception {
+ byAuthorOrCommitterFullText("author:");
+ }
+
+ @Test
+ public void byCommitterExact() throws Exception {
+ byAuthorOrCommitterExact("committer:");
+ }
+
+ @Test
+ public void byCommitterFullText() throws Exception {
+ byAuthorOrCommitterFullText("committer:");
+ }
+
+ private void byAuthorOrCommitterExact(String searchOperator) throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
+ PersonIdent john = new PersonIdent("John", "john@example.com");
+ PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
+ Change change1 = createChange(repo, johnDoe);
+ Change change2 = createChange(repo, john);
+ Change change3 = createChange(repo, doeSmith);
- // By exact email address
- assertQuery("committer:jcommitter@example.com", change1);
+ // Only email address.
+ assertQuery(searchOperator + "john.doe@example.com", change1);
+ assertQuery(searchOperator + "john@example.com", change2);
+ assertQuery(searchOperator + "Doe_SmIth@example.com", change3); // Case insensitive.
- // By email address part
- assertQuery("committer:jcommitter", change1);
- assertQuery("committer:example", change1);
- assertQuery("committer:example.com", change1);
+ // Right combination of email address and name.
+ assertQuery(searchOperator + "\"John Doe <john.doe@example.com>\"", change1);
+ assertQuery(searchOperator + "\" John <john@example.com> \"", change2);
+ assertQuery(searchOperator + "\"doE SMITH <doe_smitH@example.com>\"", change3);
- // By name part
- assertQuery("committer:Committer", change1);
+ // Wrong combination of email address and name.
+ assertQuery(searchOperator + "\"John <john.doe@example.com>\"");
+ assertQuery(searchOperator + "\"Doe John <john@example.com>\"");
+ assertQuery(searchOperator + "\"Doe John <doe_smith@example.com>\"");
+ }
- // Case insensitive
- assertQuery("committer:jCoMmItTeR", change1);
- assertQuery("committer:ExAmPlE", change1);
+ private void byAuthorOrCommitterFullText(String searchOperator) throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
+ PersonIdent john = new PersonIdent("John", "john@example.com");
+ PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
+ Change change1 = createChange(repo, johnDoe);
+ Change change2 = createChange(repo, john);
+ Change change3 = createChange(repo, doeSmith);
- // By non-existing email address / name / part
- assertQuery("committer:jauthor@example.com");
- assertQuery("committer:somewhere.com");
- assertQuery("committer:jauthor");
- assertQuery("committer:Author");
+ // By exact name.
+ assertQuery(searchOperator + "\"John Doe\"", change1);
+ assertQuery(searchOperator + "\"john\"", change2, change1);
+ assertQuery(searchOperator + "\"Doe smith\"", change3);
+
+ // By name part.
+ assertQuery(searchOperator + "Doe", change3, change1);
+ assertQuery(searchOperator + "smith", change3);
+
+ // By wrong combination.
+ assertQuery(searchOperator + "\"John Smith\"");
+
+ // By invalid query.
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("invalid value");
+ // SchemaUtil.getNameParts will return an empty set for query only containing these characters.
+ assertQuery(searchOperator + "@.- /_");
+ }
+
+ private Change createChange(TestRepository<Repo> repo, PersonIdent person) throws Exception {
+ RevCommit commit =
+ repo.parseBody(repo.commit().message("message").author(person).committer(person).create());
+ return insert(repo, newChangeForCommit(repo, commit), null);
}
@Test
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index ce444c5..01f61d9 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -2,8 +2,10 @@
## Installing [Node.js](https://nodejs.org/en/download/)
+The minimum nodejs version supported is 6.x+
+
```sh
-# Debian/Ubuntu
+# Debian experimental
sudo apt-get install nodejs-legacy
# OS X with Homebrew
@@ -146,3 +148,17 @@
`eslint --ext .html,.js polygerrit-ui/app/$YOUR_DIR_HERE`
* To run the linter on all of your local changes:
`git diff --name-only master | xargs eslint --ext .html,.js`
+
+We also use the polylint tool to lint use of Polymer. To install polylint,
+execute the following command.
+
+```sh
+npm install -g polylint
+```
+
+To run polylint, execute the following command.
+
+```sh
+bazel test //polygerrit-ui/app:polylint_test
+```
+
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 4e99272..b2ce8f0 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -129,6 +129,16 @@
),
)
+filegroup(
+ name = "bower_components",
+ srcs = glob(
+ [
+ "bower_components/**/*.html",
+ "bower_components/**/*.js",
+ ]
+ ),
+)
+
genrule2(
name = "pg_code_zip",
srcs = [":pg_code"],
@@ -172,3 +182,18 @@
"manual",
],
)
+
+sh_test(
+ name = "polylint_test",
+ size = "large",
+ srcs = ["polylint_test.sh"],
+ data = [
+ ":pg_code",
+ ":bower_components",
+ ],
+ # Should not run sandboxed.
+ tags = [
+ "local",
+ "manual",
+ ],
+)
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
index adc9810..65908f2 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
@@ -18,10 +18,10 @@
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
-<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<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="../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-change-table-behavior.html">
<test-fixture id="basic">
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index aefbcb8..7ebb17a 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
<!-- Polymer included for the html import polyfill. -->
-<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../bower_components/web-component-tester/browser.js"></script>
<title>gr-patch-set-behavior</title>
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 7b94ab6..848c744 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
<!-- Polymer included for the html import polyfill. -->
-<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../bower_components/web-component-tester/browser.js"></script>
<title>gr-path-list-behavior</title>
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index a026ff9..df376ba 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -18,8 +18,8 @@
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
-<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<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="keyboard-shortcut-behavior.html">
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 2cf8c41..4ced26e 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
@@ -124,6 +124,9 @@
overflow: auto;
word-break: break-all;
}
+ #commitMessageEditor {
+ min-width: 72ch;
+ }
.editCommitMessage {
margin-top: 1em;
}
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 f1613e5..80bf06c 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
@@ -36,6 +36,7 @@
display: block;
}
.row {
+ border-top: 1px solid #eee;
display: flex;
padding: .1em .25em;
}
@@ -334,8 +335,10 @@
</template>
</template>
</div>
- <div class$="row totalChanges [[_computeExpandInlineClass(_userPrefs)]]">
- <div class="total-stats" hidden$="[[_hideChangeTotals]]">
+ <div
+ class$="row totalChanges [[_computeExpandInlineClass(_userPrefs)]]"
+ hidden$="[[_hideChangeTotals]]">
+ <div class="total-stats">
<span
class="added"
tabindex="0"
@@ -350,8 +353,10 @@
</span>
</div>
</div>
- <div class$="row totalChanges [[_computeExpandInlineClass(_userPrefs)]]">
- <div class="total-stats" hidden$="[[_hideBinaryChangeTotals]]">
+ <div
+ class$="row totalChanges [[_computeExpandInlineClass(_userPrefs)]]"
+ hidden$="[[_hideBinaryChangeTotals]]">
+ <div class="total-stats">
<span class="added" aria-label="Total lines added">
[[_formatBytes(_patchChange.size_delta_inserted)]]
[[_formatPercentage(_patchChange.total_size,
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 b167404..ac8e299 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
@@ -475,7 +475,9 @@
_handleCommentDelete() {
Polymer.dom(Gerrit.getRootElement()).appendChild(this.$.overlay);
- this.$.overlay.open();
+ this.async(() => {
+ this.$.overlay.open();
+ }, 1);
},
_handleCancelDeleteComment() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 893b7ed..ce3b233 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -269,12 +269,14 @@
assert.isTrue(element.$$('.action.delete')
.classList.contains('showDeleteButtons'));
MockInteractions.tap(element.$$('.action.delete'));
- element.$.overlay.open.lastCall.returnValue.then(() => {
- element.$.confirmDeleteComment.message = 'removal reason';
- element._handleConfirmDeleteComment();
- assert.isTrue(element.$.restAPI.deleteComment.calledWith(
- 42, 0xDEADBEEF, 'baf0414d_60047215', 'removal reason'));
- done();
+ flush(() => {
+ element.$.overlay.open.lastCall.returnValue.then(() => {
+ element.$.confirmDeleteComment.message = 'removal reason';
+ element._handleConfirmDeleteComment();
+ assert.isTrue(element.$.restAPI.deleteComment.calledWith(
+ 42, 0xDEADBEEF, 'baf0414d_60047215', 'removal reason'));
+ done();
+ });
});
});
});
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 81379af..afbce45 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -43,13 +43,16 @@
on-keydown="_handleKeydown"
on-focus="_onInputFocus"
autocomplete="off" />
- <gr-autocomplete-dropdown id="suggestions"
- on-item-selected="_handleItemSelect"
- suggestions="[[_suggestions]]"
- role="listbox"
- index="[[index]]"
- hidden$="[[_computeSuggestionsHidden(_suggestions, _focused)]]">
- </gr-autocomplete-dropdown>
+ <!-- This container is needed for Safari and Firefox -->
+ <div id="suggestionContainer">
+ <gr-autocomplete-dropdown id="suggestions"
+ on-item-selected="_handleItemSelect"
+ suggestions="[[_suggestions]]"
+ role="listbox"
+ index="[[index]]"
+ hidden$="[[_computeSuggestionsHidden(_suggestions, _focused)]]">
+ </gr-autocomplete-dropdown>
+ </div>
</template>
<script src="gr-autocomplete.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index f83e856..c92a479 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -70,12 +70,16 @@
});
test('emoji selector is not open with the textarea lacks focus', () => {
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
element.text = ':';
assert.isFalse(!element.$.emojiSuggestions.hidden);
});
test('emoji selector is not open when a general text is entered', () => {
MockInteractions.focus(element.$.textarea);
+ element.$.textarea.selectionStart = 9;
+ element.$.textarea.selectionEnd = 9;
element.text = 'some text';
assert.isFalse(!element.$.emojiSuggestions.hidden);
});
@@ -83,10 +87,16 @@
test('emoji selector opens when a colon is typed & the textarea has focus',
() => {
MockInteractions.focus(element.$.textarea);
- flushAsynchronousOperations();
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
element.text = ':';
+ element.$.textarea.selectionStart = 2;
+ element.$.textarea.selectionEnd = 2;
element.text = ':t';
- assert.isTrue(!element.$.emojiSuggestions.hidden);
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.emojiSuggestions.hidden);
assert.equal(element._colonIndex, 0);
assert.isFalse(element._hideAutocomplete);
assert.equal(element._currentSearchString, 't');
@@ -96,8 +106,14 @@
const resetStub = sandbox.stub(element, '_resetEmojiDropdown');
MockInteractions.focus(element.$.textarea);
flushAsynchronousOperations();
+ element.$.textarea.selectionStart = 10;
+ element.$.textarea.selectionEnd = 10;
element.text = 'test test ';
+ element.$.textarea.selectionStart = 12;
+ element.$.textarea.selectionEnd = 12;
element.text = 'test test :';
+ element.$.textarea.selectionStart = 15;
+ element.$.textarea.selectionEnd = 15;
element.text = 'test test :smi';
assert.equal(element._currentSearchString, 'smi');
@@ -140,6 +156,8 @@
});
test('_handleEmojiSelect', () => {
+ element.$.textarea.selectionStart = 16;
+ element.$.textarea.selectionEnd = 16;
element.text = 'test test :tears';
element._colonIndex = 10;
const selectedItem = {dataset: {value: '😂'}};
@@ -149,6 +167,8 @@
});
test('_getPositionOfCursor', () => {
+ element.$.textarea.selectionStart = 4;
+ element.$.textarea.selectionEnd = 4;
element.text = 'test';
element._getPositionOfCursor();
assert.deepEqual(element.$.hiddenText.innerHTML, element.text +
@@ -178,7 +198,11 @@
function setupDropdown() {
MockInteractions.focus(element.$.textarea);
flushAsynchronousOperations();
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
element.text = ':';
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
element.text = ':1';
}
diff --git a/polygerrit-ui/app/lint_test.sh b/polygerrit-ui/app/lint_test.sh
index 7ee74d8..35939ba 100755
--- a/polygerrit-ui/app/lint_test.sh
+++ b/polygerrit-ui/app/lint_test.sh
@@ -3,7 +3,7 @@
set -ex
eslint_bin=$(which npm)
-if [[ -z "$eslint_bin" ]]; then
+if [ -z "$eslint_bin" ]; then
echo "NPM must be on the path."
exit 1
fi
@@ -11,7 +11,7 @@
eslint_bin=$(which eslint)
eslint_config=$(npm list -g | grep -c eslint-config-google)
eslint_plugin=$(npm list -g | grep -c eslint-plugin-html)
-if [[ -z "$eslint_bin" ]] || [[ eslint_config -eq "0" ]] || [[ eslint_plugin -eq "0" ]]; then
+if [ -z "$eslint_bin" ] || [ "$eslint_config" -eq "0" ] || [ "$eslint_plugin" -eq "0" ]; then
echo "You must install ESLint and its dependencies from NPM."
echo "> npm install -g eslint eslint-config-google eslint-plugin-html"
echo "For more information, view the README:"
diff --git a/polygerrit-ui/app/polylint_test.sh b/polygerrit-ui/app/polylint_test.sh
new file mode 100755
index 0000000..9f136a6
--- /dev/null
+++ b/polygerrit-ui/app/polylint_test.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -ex
+
+npm_bin=$(which npm)
+if [[ -z "$npm_bin" ]]; then
+ echo "NPM must be on the path."
+ exit 1
+fi
+
+polylint_bin=$(which polylint)
+if [[ -z "$polylint_bin" ]]; then
+ echo "You must install polylint and its dependencies from NPM."
+ echo "> npm install -g polylint"
+ exit 1
+fi
+
+${polylint_bin} --root polygerrit-ui/app --input elements/gr-app.html