Merge "Low-level plugin event helper API"
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 40906bd..99b45a2 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -1095,8 +1095,8 @@
Note that a new label as `Is-Pure-Revert` should not be configured.
It's only used to show `'Needs Is-Pure-Revert'` in the UI to clearly
-indicate to the user that all the comments have to be resolved for the
-change to become submittable.
+indicate to the user that the change has to be a pure revert in order
+to become submittable.
== Examples - Submit Type
The following examples show how to implement own submit type rules.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index cb33959..8ac063b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -467,7 +467,7 @@
}
@Test
- public void ignoreChange() throws Exception {
+ public void ignoreChangeBySetStars() throws Exception {
TestAccount user2 = accountCreator.user2();
accountIndexedCounter.clear();
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 419b78d..7f7fe00 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
@@ -81,6 +81,7 @@
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
import com.google.gerrit.extensions.api.changes.ReviewResult;
import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.ChangeKind;
@@ -122,6 +123,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
@@ -3428,4 +3430,90 @@
assertThat(trackingIds.iterator().next().system).isEqualTo("JIRA");
assertThat(trackingIds.iterator().next().id).isEqualTo("JRA001");
}
+
+ @Test
+ public void ignore() throws Exception {
+ TestAccount user2 = accountCreator.user2();
+
+ PushOneCommit.Result r = createChange();
+
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email;
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+
+ in = new AddReviewerInput();
+ in.reviewer = user2.email;
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+
+ setApiUser(user);
+ gApi.changes().id(r.getChangeId()).ignore(true);
+ assertThat(gApi.changes().id(r.getChangeId()).ignored()).isTrue();
+
+ sender.clear();
+ setApiUser(admin);
+ gApi.changes().id(r.getChangeId()).abandon();
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ assertThat(messages.get(0).rcpt()).containsExactly(user2.emailAddress);
+
+ setApiUser(user);
+ gApi.changes().id(r.getChangeId()).ignore(false);
+ assertThat(gApi.changes().id(r.getChangeId()).ignored()).isFalse();
+ }
+
+ @Test
+ public void cannotIgnoreOwnChange() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("cannot ignore own change");
+ gApi.changes().id(changeId).ignore(true);
+ }
+
+ @Test
+ public void cannotIgnoreStarredChange() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ setApiUser(user);
+ gApi.accounts().self().starChange(changeId);
+ assertThat(gApi.changes().id(changeId).get().starred).isTrue();
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "The labels "
+ + StarredChangesUtil.DEFAULT_LABEL
+ + " and "
+ + StarredChangesUtil.IGNORE_LABEL
+ + " are mutually exclusive. Only one of them can be set.");
+ gApi.changes().id(changeId).ignore(true);
+ }
+
+ @Test
+ public void cannotStarIgnoredChange() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ setApiUser(user);
+ gApi.changes().id(changeId).ignore(true);
+ assertThat(gApi.changes().id(changeId).ignored()).isTrue();
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "The labels "
+ + StarredChangesUtil.DEFAULT_LABEL
+ + " and "
+ + StarredChangesUtil.IGNORE_LABEL
+ + " are mutually exclusive. Only one of them can be set.");
+ gApi.accounts().self().starChange(changeId);
+ }
+
+ @Test
+ public void cannotSetInvalidLabel() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // label cannot contain whitespace
+ String invalidLabel = "invalid label";
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("invalid labels: " + invalidLabel);
+ gApi.accounts().self().setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel)));
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 9bc09d2..7081245 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -112,6 +112,13 @@
void ignore(boolean ignore) throws RestApiException;
/**
+ * Check if this change is ignored.
+ *
+ * @return true if the change is ignored
+ */
+ boolean ignored() throws RestApiException;
+
+ /**
* Mute or un-mute this change.
*
* @param mute mute the change if true
@@ -562,6 +569,11 @@
}
@Override
+ public boolean ignored() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void mute(boolean mute) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index c1f0989..61ab3a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -33,6 +33,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
@@ -122,26 +123,33 @@
}
}
- public static class IllegalLabelException extends IllegalArgumentException {
+ public static class IllegalLabelException extends Exception {
private static final long serialVersionUID = 1L;
- static IllegalLabelException invalidLabels(Set<String> invalidLabels) {
- return new IllegalLabelException(
- String.format("invalid labels: %s", Joiner.on(", ").join(invalidLabels)));
- }
-
- static IllegalLabelException mutuallyExclusiveLabels(String label1, String label2) {
- return new IllegalLabelException(
- String.format(
- "The labels %s and %s are mutually exclusive. Only one of them can be set.",
- label1, label2));
- }
-
IllegalLabelException(String message) {
super(message);
}
}
+ public static class InvalidLabelsException extends IllegalLabelException {
+ private static final long serialVersionUID = 1L;
+
+ InvalidLabelsException(Set<String> invalidLabels) {
+ super(String.format("invalid labels: %s", Joiner.on(", ").join(invalidLabels)));
+ }
+ }
+
+ public static class MutuallyExclusiveLabelsException extends IllegalLabelException {
+ private static final long serialVersionUID = 1L;
+
+ MutuallyExclusiveLabelsException(String label1, String label2) {
+ super(
+ String.format(
+ "The labels %s and %s are mutually exclusive. Only one of them can be set.",
+ label1, label2));
+ }
+ }
+
private static final Logger log = LoggerFactory.getLogger(StarredChangesUtil.class);
public static final String DEFAULT_LABEL = "star";
@@ -192,7 +200,7 @@
Change.Id changeId,
Set<String> labelsToAdd,
Set<String> labelsToRemove)
- throws OrmException {
+ throws OrmException, IllegalLabelException {
try (Repository repo = repoManager.openRepository(allUsers)) {
String refName = RefNames.refsStarredChanges(changeId, accountId);
StarRef old = readLabels(repo, refName);
@@ -296,26 +304,38 @@
}
}
- public void ignore(Account.Id accountId, Project.NameKey project, Change.Id changeId)
- throws OrmException {
- star(accountId, project, changeId, ImmutableSet.of(IGNORE_LABEL), ImmutableSet.of());
+ public void ignore(ChangeResource rsrc) throws OrmException, IllegalLabelException {
+ star(
+ rsrc.getUser().asIdentifiedUser().getAccountId(),
+ rsrc.getProject(),
+ rsrc.getChange().getId(),
+ ImmutableSet.of(IGNORE_LABEL),
+ ImmutableSet.of());
}
- public void unignore(Account.Id accountId, Project.NameKey project, Change.Id changeId)
- throws OrmException {
- star(accountId, project, changeId, ImmutableSet.of(), ImmutableSet.of(IGNORE_LABEL));
+ public void unignore(ChangeResource rsrc) throws OrmException, IllegalLabelException {
+ star(
+ rsrc.getUser().asIdentifiedUser().getAccountId(),
+ rsrc.getProject(),
+ rsrc.getChange().getId(),
+ ImmutableSet.of(),
+ ImmutableSet.of(IGNORE_LABEL));
}
public boolean isIgnoredBy(Change.Id changeId, Account.Id accountId) throws OrmException {
return getLabels(accountId, changeId).contains(IGNORE_LABEL);
}
+ public boolean isIgnored(ChangeResource rsrc) throws OrmException {
+ return isIgnoredBy(rsrc.getChange().getId(), rsrc.getUser().asIdentifiedUser().getAccountId());
+ }
+
private static String getMuteLabel(Change change) {
return MUTE_LABEL + "/" + change.currentPatchSetId().get();
}
public void mute(Account.Id accountId, Project.NameKey project, Change change)
- throws OrmException {
+ throws OrmException, IllegalLabelException {
star(
accountId,
project,
@@ -325,7 +345,7 @@
}
public void unmute(Account.Id accountId, Project.NameKey project, Change change)
- throws OrmException {
+ throws OrmException, IllegalLabelException {
star(
accountId,
project,
@@ -355,7 +375,7 @@
}
public static ObjectId writeLabels(Repository repo, Collection<String> labels)
- throws IOException {
+ throws IOException, InvalidLabelsException {
validateLabels(labels);
try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id =
@@ -367,13 +387,14 @@
}
}
- private static void checkMutuallyExclusiveLabels(Set<String> labels) {
+ private static void checkMutuallyExclusiveLabels(Set<String> labels)
+ throws MutuallyExclusiveLabelsException {
if (labels.containsAll(ImmutableSet.of(DEFAULT_LABEL, IGNORE_LABEL))) {
- throw IllegalLabelException.mutuallyExclusiveLabels(DEFAULT_LABEL, IGNORE_LABEL);
+ throw new MutuallyExclusiveLabelsException(DEFAULT_LABEL, IGNORE_LABEL);
}
}
- private static void validateLabels(Collection<String> labels) {
+ private static void validateLabels(Collection<String> labels) throws InvalidLabelsException {
if (labels == null) {
return;
}
@@ -385,13 +406,13 @@
}
}
if (!invalidLabels.isEmpty()) {
- throw IllegalLabelException.invalidLabels(invalidLabels);
+ throw new InvalidLabelsException(invalidLabels);
}
}
private void updateLabels(
Repository repo, String refName, ObjectId oldObjectId, Collection<String> labels)
- throws IOException, OrmException {
+ throws IOException, OrmException, InvalidLabelsException {
try (RevWalk rw = new RevWalk(repo)) {
RefUpdate u = repo.updateRef(refName);
u.setExpectedOldObjectId(oldObjectId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
index e3c4bb1..ad73a69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
@@ -20,8 +20,10 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -30,6 +32,8 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
+import com.google.gerrit.server.StarredChangesUtil.MutuallyExclusiveLabelsException;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -129,7 +133,7 @@
@Override
public Response<?> apply(AccountResource rsrc, EmptyInput in)
- throws AuthException, OrmException, IOException {
+ throws RestApiException, OrmException, IOException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed to add starred change");
}
@@ -140,6 +144,10 @@
change.getId(),
StarredChangesUtil.DEFAULT_LABELS,
null);
+ } catch (MutuallyExclusiveLabelsException e) {
+ throw new ResourceConflictException(e.getMessage());
+ } catch (IllegalLabelException e) {
+ throw new BadRequestException(e.getMessage());
} catch (OrmDuplicateKeyException e) {
return Response.none();
}
@@ -179,7 +187,7 @@
@Override
public Response<?> apply(AccountResource.StarredChange rsrc, EmptyInput in)
- throws AuthException, OrmException, IOException {
+ throws AuthException, OrmException, IOException, IllegalLabelException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed remove starred change");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 2dc9f6c..d006c7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -48,6 +48,8 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeIncludedIn;
import com.google.gerrit.server.change.ChangeJson;
@@ -89,6 +91,7 @@
import com.google.gerrit.server.change.Unignore;
import com.google.gerrit.server.change.Unmute;
import com.google.gerrit.server.change.WorkInProgressOp;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@@ -145,6 +148,7 @@
private final SetReadyForReview setReady;
private final PutMessage putMessage;
private final GetPureRevert getPureRevert;
+ private final StarredChangesUtil stars;
@Inject
ChangeApiImpl(
@@ -190,6 +194,7 @@
SetReadyForReview setReady,
PutMessage putMessage,
GetPureRevert getPureRevert,
+ StarredChangesUtil stars,
@Assisted ChangeResource change) {
this.changeApi = changeApi;
this.revert = revert;
@@ -233,6 +238,7 @@
this.setReady = setReady;
this.putMessage = putMessage;
this.getPureRevert = getPureRevert;
+ this.stars = stars;
this.change = change;
}
@@ -657,10 +663,23 @@
public void ignore(boolean ignore) throws RestApiException {
// TODO(dborowitz): Convert to RetryingRestModifyView. Needs to plumb BatchUpdate.Factory into
// StarredChangesUtil.
- if (ignore) {
- this.ignore.apply(change, new Ignore.Input());
- } else {
- unignore.apply(change, new Unignore.Input());
+ try {
+ if (ignore) {
+ this.ignore.apply(change, new Ignore.Input());
+ } else {
+ unignore.apply(change, new Unignore.Input());
+ }
+ } catch (OrmException | IllegalLabelException e) {
+ throw asRestApiException("Cannot ignore change", e);
+ }
+ }
+
+ @Override
+ public boolean ignored() throws RestApiException {
+ try {
+ return stars.isIgnoredBy(change.getId(), change.getUser().getAccountId());
+ } catch (OrmException e) {
+ throw asRestApiException("Cannot check if ignored", e);
}
}
@@ -668,10 +687,14 @@
public void mute(boolean mute) throws RestApiException {
// TODO(dborowitz): Convert to RetryingRestModifyView. Needs to plumb BatchUpdate.Factory into
// StarredChangesUtil.
- if (mute) {
- this.mute.apply(change, new Mute.Input());
- } else {
- unmute.apply(change, new Unmute.Input());
+ try {
+ if (mute) {
+ this.mute.apply(change, new Mute.Input());
+ } else {
+ unmute.apply(change, new Unmute.Input());
+ }
+ } catch (OrmException | IllegalLabelException e) {
+ throw asRestApiException("Cannot mute change", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
index a955abe..fb2fb27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
@@ -59,14 +59,7 @@
return new ListRequest() {
@Override
public SortedMap<String, PluginInfo> getAsMap() throws RestApiException {
- ListPlugins list = listProvider.get();
- list.setAll(this.getAll());
- list.setStart(this.getStart());
- list.setLimit(this.getLimit());
- list.setMatchPrefix(this.getPrefix());
- list.setMatchSubstring(this.getSubstring());
- list.setMatchRegex(this.getRegex());
- return list.apply(TopLevelResource.INSTANCE);
+ return listProvider.get().request(this).apply(TopLevelResource.INSTANCE);
}
};
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index a206a49..60dc474 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -62,6 +62,7 @@
import com.google.gerrit.server.project.PutConfig;
import com.google.gerrit.server.project.PutDescription;
import com.google.gerrit.server.project.SetAccess;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.List;
@@ -92,8 +93,8 @@
private final CreateAccessChange createAccessChange;
private final GetConfig getConfig;
private final PutConfig putConfig;
- private final ListBranches listBranches;
- private final ListTags listTags;
+ private final Provider<ListBranches> listBranches;
+ private final Provider<ListTags> listTags;
private final DeleteBranches deleteBranches;
private final DeleteTags deleteTags;
private final CommitsCollection commitsCollection;
@@ -119,8 +120,8 @@
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
- ListBranches listBranches,
- ListTags listTags,
+ Provider<ListBranches> listBranches,
+ Provider<ListTags> listTags,
DeleteBranches deleteBranches,
DeleteTags deleteTags,
CommitsCollection commitsCollection,
@@ -175,8 +176,8 @@
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
- ListBranches listBranches,
- ListTags listTags,
+ Provider<ListBranches> listBranches,
+ Provider<ListTags> listTags,
DeleteBranches deleteBranches,
DeleteTags deleteTags,
CommitsCollection commitsCollection,
@@ -230,8 +231,8 @@
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
- ListBranches listBranches,
- ListTags listTags,
+ Provider<ListBranches> listBranches,
+ Provider<ListTags> listTags,
DeleteBranches deleteBranches,
DeleteTags deleteTags,
ProjectResource project,
@@ -354,46 +355,29 @@
return new ListRefsRequest<BranchInfo>() {
@Override
public List<BranchInfo> get() throws RestApiException {
- return listBranches(this);
+ try {
+ return listBranches.get().request(this).apply(checkExists());
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list branches", e);
+ }
}
};
}
- private List<BranchInfo> listBranches(ListRefsRequest<BranchInfo> request)
- throws RestApiException {
- listBranches.setLimit(request.getLimit());
- listBranches.setStart(request.getStart());
- listBranches.setMatchSubstring(request.getSubstring());
- listBranches.setMatchRegex(request.getRegex());
- try {
- return listBranches.apply(checkExists());
- } catch (Exception e) {
- throw asRestApiException("Cannot list branches", e);
- }
- }
-
@Override
public ListRefsRequest<TagInfo> tags() {
return new ListRefsRequest<TagInfo>() {
@Override
public List<TagInfo> get() throws RestApiException {
- return listTags(this);
+ try {
+ return listTags.get().request(this).apply(checkExists());
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list tags", e);
+ }
}
};
}
- private List<TagInfo> listTags(ListRefsRequest<TagInfo> request) throws RestApiException {
- listTags.setLimit(request.getLimit());
- listTags.setStart(request.getStart());
- listTags.setMatchSubstring(request.getSubstring());
- listTags.setMatchRegex(request.getRegex());
- try {
- return listTags.apply(checkExists());
- } catch (Exception e) {
- throw asRestApiException("Cannot list tags", e);
- }
- }
-
@Override
public List<ProjectInfo> children() throws RestApiException {
return children(false);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
index 83ab811..46dabdf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
@@ -14,15 +14,17 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
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.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
+import com.google.gerrit.server.StarredChangesUtil.MutuallyExclusiveLabelsException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,12 +36,10 @@
public static class Input {}
- private final Provider<IdentifiedUser> self;
private final StarredChangesUtil stars;
@Inject
- Ignore(Provider<IdentifiedUser> self, StarredChangesUtil stars) {
- this.self = self;
+ Ignore(StarredChangesUtil stars) {
this.stars = stars;
}
@@ -48,26 +48,33 @@
return new UiAction.Description()
.setLabel("Ignore")
.setTitle("Ignore the change")
- .setVisible(!rsrc.isUserOwner() && !isIgnored(rsrc));
+ .setVisible(canIgnore(rsrc));
}
@Override
- public Response<String> apply(ChangeResource rsrc, Input input) throws RestApiException {
+ public Response<String> apply(ChangeResource rsrc, Input input)
+ throws RestApiException, OrmException, IllegalLabelException {
try {
- if (rsrc.isUserOwner() || isIgnored(rsrc)) {
- // early exit for own changes and already ignored changes
- return Response.ok("");
+ if (rsrc.isUserOwner()) {
+ throw new BadRequestException("cannot ignore own change");
}
- stars.ignore(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange().getId());
- } catch (OrmException e) {
- throw new RestApiException("failed to ignore change", e);
+
+ if (!isIgnored(rsrc)) {
+ stars.ignore(rsrc);
+ }
+ return Response.ok("");
+ } catch (MutuallyExclusiveLabelsException e) {
+ throw new ResourceConflictException(e.getMessage());
}
- return Response.ok("");
+ }
+
+ private boolean canIgnore(ChangeResource rsrc) {
+ return !rsrc.isUserOwner() && !isIgnored(rsrc);
}
private boolean isIgnored(ChangeResource rsrc) {
try {
- return stars.isIgnoredBy(rsrc.getChange().getId(), self.get().getAccountId());
+ return stars.isIgnored(rsrc);
} catch (OrmException e) {
log.error("failed to check ignored star", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mute.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mute.java
index d14fec8..5fb3ef6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mute.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -52,16 +53,13 @@
}
@Override
- public Response<String> apply(ChangeResource rsrc, Input input) throws RestApiException {
- try {
- if (rsrc.isUserOwner() || isMuted(rsrc.getChange())) {
- // early exit for own changes and already muted changes
- return Response.ok("");
- }
- stars.mute(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange());
- } catch (OrmException e) {
- throw new RestApiException("failed to mute change", e);
+ public Response<String> apply(ChangeResource rsrc, Input input)
+ throws RestApiException, OrmException, IllegalLabelException {
+ if (rsrc.isUserOwner() || isMuted(rsrc.getChange())) {
+ // early exit for own changes and already muted changes
+ return Response.ok("");
}
+ stars.mute(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange());
return Response.ok("");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
index 081fc22..a10e754 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
@@ -18,11 +18,10 @@
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.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,12 +33,10 @@
public static class Input {}
- private final Provider<IdentifiedUser> self;
private final StarredChangesUtil stars;
@Inject
- Unignore(Provider<IdentifiedUser> self, StarredChangesUtil stars) {
- this.self = self;
+ Unignore(StarredChangesUtil stars) {
this.stars = stars;
}
@@ -48,26 +45,21 @@
return new UiAction.Description()
.setLabel("Unignore")
.setTitle("Unignore the change")
- .setVisible(!rsrc.isUserOwner() && isIgnored(rsrc));
+ .setVisible(isIgnored(rsrc));
}
@Override
- public Response<String> apply(ChangeResource rsrc, Input input) throws RestApiException {
- try {
- if (rsrc.isUserOwner() || !isIgnored(rsrc)) {
- // early exit for own changes and not ignored changes
- return Response.ok("");
- }
- stars.unignore(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange().getId());
- } catch (OrmException e) {
- throw new RestApiException("failed to unignore change", e);
+ public Response<String> apply(ChangeResource rsrc, Input input)
+ throws RestApiException, OrmException, IllegalLabelException {
+ if (isIgnored(rsrc)) {
+ stars.unignore(rsrc);
}
return Response.ok("");
}
private boolean isIgnored(ChangeResource rsrc) {
try {
- return stars.isIgnoredBy(rsrc.getChange().getId(), self.get().getAccountId());
+ return stars.isIgnored(rsrc);
} catch (OrmException e) {
log.error("failed to check ignored star", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unmute.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unmute.java
index 49b41cb..586f3e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unmute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unmute.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -53,16 +54,13 @@
}
@Override
- public Response<String> apply(ChangeResource rsrc, Input input) throws RestApiException {
- try {
- if (rsrc.isUserOwner() || !isMuted(rsrc.getChange())) {
- // early exit for own changes and not muted changes
- return Response.ok("");
- }
- stars.unmute(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange());
- } catch (OrmException e) {
- throw new RestApiException("failed to unmute change", e);
+ public Response<String> apply(ChangeResource rsrc, Input input)
+ throws RestApiException, OrmException, IllegalLabelException {
+ if (rsrc.isUserOwner() || !isMuted(rsrc.getChange())) {
+ // early exit for own changes and not muted changes
+ return Response.ok("");
}
+ stars.unmute(self.get().getAccountId(), rsrc.getProject(), rsrc.getChange());
return Response.ok("");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 97d728d..9a42bb9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.plugins.Plugins;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -104,6 +105,16 @@
this.pluginLoader = pluginLoader;
}
+ public ListPlugins request(Plugins.ListRequest request) {
+ this.setAll(request.getAll());
+ this.setStart(request.getStart());
+ this.setLimit(request.getLimit());
+ this.setMatchPrefix(request.getPrefix());
+ this.setMatchSubstring(request.getSubstring());
+ this.setMatchRegex(request.getRegex());
+ return this;
+ }
+
@Override
public SortedMap<String, PluginInfo> apply(TopLevelResource resource) throws BadRequestException {
Stream<Plugin> s = Streams.stream(pluginLoader.getPlugins(all));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index b7af949..b2edc6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.api.projects.BranchInfo;
+import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -119,6 +120,14 @@
this.webLinks = webLinks;
}
+ public ListBranches request(ListRefsRequest<BranchInfo> request) {
+ this.setLimit(request.getLimit());
+ this.setStart(request.getStart());
+ this.setMatchSubstring(request.getSubstring());
+ this.setMatchRegex(request.getRegex());
+ return this;
+ }
+
@Override
public List<BranchInfo> apply(ProjectResource rsrc)
throws ResourceNotFoundException, IOException, BadRequestException,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
index 302f8e6..a58f316 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.project;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -114,6 +115,14 @@
this.links = webLinks;
}
+ public ListTags request(ListRefsRequest<TagInfo> request) {
+ this.setLimit(request.getLimit());
+ this.setStart(request.getStart());
+ this.setMatchSubstring(request.getSubstring());
+ this.setMatchRegex(request.getRegex());
+ return this;
+ }
+
@Override
public List<TagInfo> apply(ProjectResource resource)
throws IOException, ResourceNotFoundException, BadRequestException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_123.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_123.java
index ec63141..31cfd5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_123.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_123.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -78,7 +79,7 @@
ObjectId.zeroId(), id, RefNames.refsStarredChanges(e.getValue(), e.getKey())));
}
bru.execute(rw, new TextProgressMonitor());
- } catch (IOException ex) {
+ } catch (IOException | IllegalLabelException ex) {
throw new OrmException(ex);
}
}
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index b6f8d99..63761f2 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -2,7 +2,7 @@
_JGIT_VERS = "4.8.0.201706111038-r.71-g45da0fc6f"
-_DOC_VERS = _JGIT_VERS # Set to _JGIT_VERS unless using a snapshot
+_DOC_VERS = "4.8.0.201706111038-r" # Set to _JGIT_VERS unless using a snapshot
JGIT_DOC_URL = "http://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index 5110a28..9936730 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -46,6 +46,17 @@
patchNumEquals(a, b) {
return a + '' === b + '';
},
+
+ /**
+ * Whether the given patch is a numbered parent of a merge (i.e. a negative
+ * number).
+ * @param {string|number} n
+ * @return {Boolean}
+ */
+ isMergeParent(n) {
+ return (n + '')[0] === '-';
+ },
+
/**
* Given an object of revisions, get a particular revision based on patch
* num.
@@ -192,6 +203,13 @@
return allPatchSets[allPatchSets.length - 1].num;
},
+ /** @return {Boolean} */
+ hasEditBasedOnCurrentPatchSet(allPatchSets) {
+ if (!allPatchSets || !allPatchSets.length) { return false; }
+ return allPatchSets[allPatchSets.length - 1].num ===
+ Gerrit.PatchSetBehavior.EDIT_NAME;
+ },
+
/**
* Check whether there is no newer patch than the latest patch that was
* available when this change was loaded.
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 c2ccccd..54c1355 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
@@ -174,6 +174,20 @@
assert.isTrue(equals('PARENT', 'PARENT'));
});
+ test('isMergeParent', () => {
+ const isParent = Gerrit.PatchSetBehavior.isMergeParent;
+ assert.isFalse(isParent(1));
+ assert.isFalse(isParent(4321));
+ assert.isFalse(isParent('52'));
+ assert.isFalse(isParent('edit'));
+ assert.isFalse(isParent('PARENT'));
+ assert.isFalse(isParent(0));
+
+ assert.isTrue(isParent(-23));
+ assert.isTrue(isParent(-1));
+ assert.isTrue(isParent('-42'));
+ });
+
test('findEditParentRevision', () => {
const findParent = Gerrit.PatchSetBehavior.findEditParentRevision;
let revisions = [
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 f2ed33a..b7a048b 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
@@ -157,6 +157,19 @@
Do you really want to delete the change?
</div>
</gr-confirm-dialog>
+ <gr-confirm-dialog
+ id="confirmDeleteEditDialog"
+ class="confirmDialog"
+ confirm-label="Delete"
+ on-cancel="_handleConfirmDialogCancel"
+ on-confirm="_handleDeleteEditConfirm">
+ <div class="header">
+ Delete Change Edit
+ </div>
+ <div class="main">
+ Do you really want to delete the edit?
+ </div>
+ </gr-confirm-dialog>
</gr-overlay>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index ff77142..5657cbe 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -51,11 +51,14 @@
const ChangeActions = {
ABANDON: 'abandon',
DELETE: '/',
+ DELETE_EDIT: 'deleteEdit',
IGNORE: 'ignore',
MOVE: 'move',
MUTE: 'mute',
PRIVATE: 'private',
PRIVATE_DELETE: 'private.delete',
+ PUBLISH_EDIT: 'publishEdit',
+ REBASE_EDIT: 'rebaseEdit',
RESTORE: 'restore',
REVERT: 'revert',
UNIGNORE: 'unignore',
@@ -118,6 +121,36 @@
__type: 'revision',
};
+ const REBASE_EDIT = {
+ enabled: true,
+ label: 'Rebase Edit',
+ title: 'Rebase change edit',
+ __key: 'rebaseEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'POST',
+ };
+
+ const PUBLISH_EDIT = {
+ enabled: true,
+ label: 'Publish Edit',
+ title: 'Publish change edit',
+ __key: 'publishEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'POST',
+ };
+
+ const DELETE_EDIT = {
+ enabled: true,
+ label: 'Delete Edit',
+ title: 'Delete change edit',
+ __key: 'deleteEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'DELETE',
+ };
+
const AWAIT_CHANGE_ATTEMPTS = 5;
const AWAIT_CHANGE_TIMEOUT_MS = 1000;
@@ -276,6 +309,14 @@
type: Array,
value() { return []; },
},
+ editLoaded: {
+ type: Boolean,
+ value: false,
+ },
+ editBasedOnCurrentPatchSet: {
+ type: Boolean,
+ value: true,
+ },
},
ActionType,
@@ -288,7 +329,8 @@
],
observers: [
- '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*)',
+ '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*, ' +
+ 'editLoaded, editBasedOnCurrentPatchSet, change)',
],
listeners: {
@@ -422,7 +464,8 @@
},
_actionsChanged(actionsChangeRecord, revisionActionsChangeRecord,
- additionalActionsChangeRecord) {
+ additionalActionsChangeRecord, editLoaded, editBasedOnCurrentPatchSet,
+ change) {
const additionalActions = (additionalActionsChangeRecord &&
additionalActionsChangeRecord.base) || [];
this.hidden = this._keyCount(actionsChangeRecord) === 0 &&
@@ -436,6 +479,47 @@
!revisionActions.download) {
this.set('revisionActions.download', DOWNLOAD_ACTION);
}
+
+ const changeActions = actionsChangeRecord.base || {};
+ if (Object.keys(changeActions).length !== 0) {
+ if (editLoaded) {
+ if (this.changeIsOpen(change.status)) {
+ if (editBasedOnCurrentPatchSet) {
+ if (!changeActions.publishEdit) {
+ this.set('actions.publishEdit', PUBLISH_EDIT);
+ }
+ if (changeActions.rebaseEdit) {
+ delete this.actions.rebaseEdit;
+ this.notifyPath('actions.rebaseEdit');
+ }
+ } else {
+ if (!changeActions.rebasEdit) {
+ this.set('actions.rebaseEdit', REBASE_EDIT);
+ }
+ if (changeActions.publishEdit) {
+ delete this.actions.publishEdit;
+ this.notifyPath('actions.publishEdit');
+ }
+ }
+ }
+ if (!changeActions.deleteEdit) {
+ this.set('actions.deleteEdit', DELETE_EDIT);
+ }
+ } else {
+ if (changeActions.publishEdit) {
+ delete this.actions.publishEdit;
+ this.notifyPath('actions.publishEdit');
+ }
+ if (changeActions.rebaseEdit) {
+ delete this.actions.rebaseEdit;
+ this.notifyPath('actions.rebaseEdit');
+ }
+ if (changeActions.deleteEdit) {
+ delete this.actions.deleteEdit;
+ this.notifyPath('actions.deleteEdit');
+ }
+ }
+ }
},
_getValuesFor(obj) {
@@ -638,12 +722,21 @@
case ChangeActions.DELETE:
this._handleDeleteTap();
break;
+ case ChangeActions.DELETE_EDIT:
+ this._handleDeleteEditTap();
+ break;
case ChangeActions.WIP:
this._handleWipTap();
break;
case ChangeActions.MOVE:
this._handleMoveTap();
break;
+ case ChangeActions.PUBLISH_EDIT:
+ this._handlePublishEditTap();
+ break;
+ case ChangeActions.REBASE_EDIT:
+ this._handleRebaseEditTap();
+ break;
default:
this._fireAction(this._prependSlash(key), this.actions[key], false);
}
@@ -775,6 +868,12 @@
this._fireAction('/', this.actions[ChangeActions.DELETE], false);
},
+ _handleDeleteEditConfirm() {
+ this._hideAllDialogs();
+
+ this._fireAction('/edit', this.actions.deleteEdit, false);
+ },
+
_getActionOverflowIndex(type, key) {
return this._overflowActions.findIndex(action => {
return action.type === type && action.key === key;
@@ -862,6 +961,9 @@
}
break;
case ChangeActions.WIP:
+ case ChangeActions.DELETE_EDIT:
+ case ChangeActions.PUBLISH_EDIT:
+ case ChangeActions.REBASE_EDIT:
page.show(this.changePath(this.changeNum));
break;
default:
@@ -949,10 +1051,22 @@
this._showActionDialog(this.$.confirmDeleteDialog);
},
+ _handleDeleteEditTap() {
+ this._showActionDialog(this.$.confirmDeleteEditDialog);
+ },
+
_handleWipTap() {
this._fireAction('/wip', this.actions.wip, false);
},
+ _handlePublishEditTap() {
+ this._fireAction('/edit:publish', this.actions.publishEdit, false);
+ },
+
+ _handleRebaseEditTap() {
+ this._fireAction('/edit:rebase', this.actions.rebaseEdit, false);
+ },
+
_handleHideBackgroundContent() {
this.$.mainContent.classList.add('overlayOpen');
},
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 11a2569..2c252eb 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -360,6 +360,137 @@
assert.isFalse(element.$.mainContent.classList.contains('overlayOpen'));
});
+ suite('change edits', () => {
+ let fireActionStub;
+ const deleteEditAction = {
+ enabled: true,
+ label: 'Delete Edit',
+ title: 'Delete change edit',
+ __key: 'deleteEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'DELETE',
+ };
+ const publishEditAction = {
+ enabled: true,
+ label: 'Publish Edit',
+ title: 'Publish change edit',
+ __key: 'publishEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'POST',
+ };
+ const rebaseEditAction = {
+ enabled: true,
+ label: 'Rebase Edit',
+ title: 'Rebase change edit',
+ __key: 'rebaseEdit',
+ __primary: false,
+ __type: 'change',
+ method: 'POST',
+ };
+
+ setup(() => {
+ fireActionStub = sandbox.stub(element, '_fireAction');
+ element.patchNum = 'edit';
+ element.editLoaded = true;
+ });
+
+ test('does not delete edit on action', () => {
+ element._handleDeleteEditTap();
+ assert.isFalse(fireActionStub.called);
+ });
+
+ test('shows confirm dialog for delete edit', () => {
+ element._handleDeleteEditTap();
+ assert.isFalse(element.$$('#confirmDeleteEditDialog').hidden);
+ assert.ok(element.$$('gr-button[data-action-key="deleteEdit"]'));
+ MockInteractions.tap(
+ element.$$('#confirmDeleteEditDialog').$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+ assert.isTrue(
+ fireActionStub.calledWith('/edit', deleteEditAction, false));
+ });
+
+ test('show publish edit but rebaseEdit is hidden', () => {
+ element.change = {
+ status: 'NEW',
+ };
+ const rebaseEditButton =
+ element.$$('gr-button[data-action-key="rebaseEdit"]');
+ assert.isNotOk(rebaseEditButton);
+
+ const publishEditButton =
+ element.$$('gr-button[data-action-key="publishEdit"]');
+ assert.ok(publishEditButton);
+ MockInteractions.tap(publishEditButton);
+ element._handlePublishEditTap();
+ flushAsynchronousOperations();
+
+ assert.isTrue(
+ fireActionStub.calledWith('/edit:publish', publishEditAction, false));
+ });
+
+ test('show rebase edit but publishEdit is hidden', () => {
+ element.change = {
+ status: 'NEW',
+ };
+ element.editBasedOnCurrentPatchSet = false;
+
+ const publishEditButton =
+ element.$$('gr-button[data-action-key="publishEdit"]');
+ assert.isNotOk(publishEditButton);
+
+ const rebaseEditButton =
+ element.$$('gr-button[data-action-key="rebaseEdit"]');
+ assert.ok(rebaseEditButton);
+ MockInteractions.tap(rebaseEditButton);
+ element._handleRebaseEditTap();
+ flushAsynchronousOperations();
+
+ assert.isTrue(
+ fireActionStub.calledWith('/edit:rebase', rebaseEditAction, false));
+ });
+
+ test('hide publishEdit and rebaseEdit if change is not open', () => {
+ element.change = {
+ status: 'MERGED',
+ };
+ flushAsynchronousOperations();
+
+ const publishEditButton =
+ element.$$('gr-button[data-action-key="publishEdit"]');
+ assert.isNotOk(publishEditButton);
+
+ const rebaseEditButton =
+ element.$$('gr-button[data-action-key="rebaseEdit"]');
+ assert.isNotOk(rebaseEditButton);
+
+ const deleteEditButton =
+ element.$$('gr-button[data-action-key="deleteEdit"]');
+ assert.ok(deleteEditButton);
+ });
+
+ test('do not show delete edit on a non change edit', () => {
+ element.editLoaded = false;
+ flushAsynchronousOperations();
+ const deleteEditButton =
+ element.$$('gr-button[data-action-key="deleteEdit"]');
+ assert.isNotOk(deleteEditButton);
+ });
+
+ test('do not show publish edit on a non change edit', () => {
+ element.change = {
+ status: 'NEW',
+ };
+ element.editLoaded = false;
+ flushAsynchronousOperations();
+ const publishEditButton =
+ element.$$('gr-button[data-action-key="publishEdit"]');
+ assert.isNotOk(publishEditButton);
+ });
+ });
+
suite('cherry-pick', () => {
let fireActionStub;
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 dbba7ad..031acda 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
@@ -351,6 +351,8 @@
commit-num="[[_commitInfo.commit]]"
patch-num="[[computeLatestPatchNum(_allPatchSets)]]"
commit-message="[[_latestCommitMessage]]"
+ edit-loaded="[[_editLoaded]]"
+ edit-based-on-current-patch-set="[[hasEditBasedOnCurrentPatchSet(_allPatchSets)]]"
on-reload-change="_handleReloadChange"
on-download-tap="_handleOpenDownloadDialog"></gr-change-actions>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index b4ae93f..5c2422d 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -44,13 +44,15 @@
display: initial;
}
.patchInfo-header {
- padding: .5em calc(var(--default-horizontal-margin) / 2);
- }
- .patchInfo-header {
background-color: #f6f6f6;
border-bottom: 1px solid #ebebeb;
display: flex;
- justify-content: space-between;
+ padding: .5em calc(var(--default-horizontal-margin) / 2);
+ }
+ .patchInfo-header-wrapper {
+ align-items: center;
+ display: flex;
+ width: 100%;
}
.latestPatchContainer {
display: none;
@@ -63,11 +65,9 @@
}
#diffPrefsContainer,
.rightControls {
+ align-self: flex-end;
margin: auto 0 auto auto;
}
- .patchInfo-header-wrapper {
- width: 100%;
- }
.showOnEdit {
display: none;
}
@@ -78,12 +78,13 @@
display: initial;
}
.fileList-header {
+ align-items: center;
display: flex;
font-weight: bold;
- justify-content: space-between;
- margin: .5em calc(var(--default-horizontal-margin) / 2);
+ margin: .5em calc(var(--default-horizontal-margin) / 2) 0;
}
.rightControls {
+ align-items: center;
display: flex;
flex-wrap: wrap;
font-weight: normal;
@@ -109,40 +110,42 @@
</style>
<div class$="patchInfo-header [[_computeEditLoadedClass(editLoaded)]] [[_computePatchInfoClass(patchRange.patchNum, allPatchSets)]]">
<div class="patchInfo-header-wrapper">
- <gr-patch-range-select
- id="rangeSelect"
- comments="[[comments]]"
- change-num="[[changeNum]]"
- patch-range="[[patchRange]]"
- available-patches="[[allPatchSets]]"
- revisions="[[change.revisions]]"
- on-patch-range-change="_handlePatchChange">
- </gr-patch-range-select>
- /
- <gr-commit-info
- change="[[change]]"
- server-config="[[serverConfig]]"
- commit-info="[[commitInfo]]"></gr-commit-info>
- <span class="latestPatchContainer">
+ <div>
+ <gr-patch-range-select
+ id="rangeSelect"
+ comments="[[comments]]"
+ change-num="[[changeNum]]"
+ patch-range="[[patchRange]]"
+ available-patches="[[allPatchSets]]"
+ revisions="[[change.revisions]]"
+ on-patch-range-change="_handlePatchChange">
+ </gr-patch-range-select>
/
- <a href$="[[changeUrl]]">Go to latest patch set</a>
- </span>
- <span class="downloadContainer desktop">
- /
- <gr-button link
- class="download"
- on-tap="_handleDownloadTap">Download</gr-button>
- </span>
- <span class="descriptionContainer hideOnEdit">
- /
- <gr-editable-label
- id="descriptionLabel"
- class="descriptionLabel"
- value="[[_computePatchSetDescription(change, patchRange.patchNum)]]"
- placeholder="[[_computeDescriptionPlaceholder(_descriptionReadOnly)]]"
- read-only="[[_descriptionReadOnly]]"
- on-changed="_handleDescriptionChanged"></gr-editable-label>
- </span>
+ <gr-commit-info
+ change="[[change]]"
+ server-config="[[serverConfig]]"
+ commit-info="[[commitInfo]]"></gr-commit-info>
+ <span class="latestPatchContainer">
+ /
+ <a href$="[[changeUrl]]">Go to latest patch set</a>
+ </span>
+ <span class="downloadContainer desktop">
+ /
+ <gr-button link
+ class="download"
+ on-tap="_handleDownloadTap">Download</gr-button>
+ </span>
+ <span class="descriptionContainer hideOnEdit">
+ /
+ <gr-editable-label
+ id="descriptionLabel"
+ class="descriptionLabel"
+ value="[[_computePatchSetDescription(change, patchRange.patchNum)]]"
+ placeholder="[[_computeDescriptionPlaceholder(_descriptionReadOnly)]]"
+ read-only="[[_descriptionReadOnly]]"
+ on-changed="_handleDescriptionChanged"></gr-editable-label>
+ </span>
+ </div>
<span id="diffPrefsContainer"
class="hideOnEdit"
hidden$="[[_computePrefsButtonHidden(diffPrefs, loggedIn)]]"
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 0a2b974..ac74ee5 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -126,7 +126,9 @@
*/
_showAlert(text, opt_actionText, opt_actionCallback,
opt_dismissOnNavigation) {
- if (this._alertElement) { return; }
+ if (this._alertElement) {
+ this._hideAlert();
+ }
this._clearHideAlertHandle();
if (opt_dismissOnNavigation) {
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index 5d1c461..17fa746 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -272,20 +272,11 @@
});
});
- test('dismissOnNavigation respected', () => {
- const asyncStub = sandbox.stub(element, 'async');
- const hideSpy = sandbox.spy(element, '_hideAlert');
- // No async call when dismissOnNavigation supplied.
- element._showAlert('test', null, null, true);
- assert.isFalse(asyncStub.called);
-
- // When page nav happens, clear alert.
- document.dispatchEvent(new CustomEvent('location-change'));
- assert.isTrue(hideSpy.called);
-
- // When timeout is not supplied, use HIDE_ALERT_TIMEOUT_MS.
- element._showAlert('test');
- assert.isTrue(asyncStub.called);
+ test('_showAlert hides existing alerts', () => {
+ element._alertElement = element._createToastAlert();
+ const hideStub = sandbox.stub(element, '_hideAlert');
+ element._showAlert();
+ assert.isTrue(hideStub.calledOnce);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index a2861c6..acc9d4f 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -91,7 +91,7 @@
QUERY_OFFSET: '/q/:query,:offset',
// Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
- CHANGE_LEGACY: /^\/c\/(\d+)\/?(((\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
+ CHANGE_LEGACY: /^\/c\/(\d+)\/?(((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
// Matches
@@ -100,15 +100,15 @@
// TODO(kaspern): Migrate completely to project based URLs, with backwards
// compatibility for change-only.
// eslint-disable-next-line max-len
- CHANGE_OR_DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/?((\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
+ CHANGE_OR_DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
// Matches
// /c/<project>/+/<changeNum>/[<basePatchNum>..]<patchNum>/<path>,edit
// eslint-disable-next-line max-len
- DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(((\d+|edit)\.\.)?(edit)(\/(.+)))),edit$/,
+ DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(((-?\d+|edit)\.\.)?(edit)(\/(.+)))),edit$/,
// Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
- DIFF_LEGACY: /^\/c\/(\d+)\/((\d+|edit)(\.\.(\d+|edit))?)\/(.+)/,
+ DIFF_LEGACY: /^\/c\/(\d+)\/((-?\d+|edit)(\.\.(\d+|edit))?)\/(.+)/,
SETTINGS: /^\/settings\/?/,
SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
@@ -298,8 +298,17 @@
const hasPatchNum = params.patchNum !== null &&
params.patchNum !== undefined;
let needsRedirect = false;
+
+ // Diffing a patch against itself is invalid, so if the base and revision
+ // patches are equal clear the base.
+ // NOTE: while selecting numbered parents of a merge is not yet
+ // implemented, normalize parent base patches to be un-selected parents in
+ // the same way.
+ // TODO(issue 4760): Remove the isMergeParent check when PG supports
+ // diffing against numbered parents of a merge.
if (hasBasePatchNum &&
- this.patchNumEquals(params.basePatchNum, params.patchNum)) {
+ (this.patchNumEquals(params.basePatchNum, params.patchNum) ||
+ this.isMergeParent(params.basePatchNum))) {
needsRedirect = true;
params.basePatchNum = null;
} else if (hasBasePatchNum && !hasPatchNum) {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index df46a38..a9231b6 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -376,6 +376,16 @@
assert.isNotOk(params.basePatchNum);
assert.equal(params.patchNum, 'edit');
});
+
+ // TODO(issue 4760): Remove when PG supports diffing against numbered
+ // parents of a merge.
+ test('range -n..m normalizes to m', () => {
+ const params = {basePatchNum: -2, patchNum: 4};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isTrue(needsRedirect);
+ assert.isNotOk(params.basePatchNum);
+ assert.equal(params.patchNum, 4);
+ });
});
});
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 4f0ebcc..bda9a35 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
@@ -15,6 +15,12 @@
'use strict';
const STORAGE_DEBOUNCE_INTERVAL = 400;
+ const TOAST_DEBOUNCE_INTERVAL = 200;
+
+ const SAVING_MESSAGE = 'Saving';
+ const DRAFT_SINGULAR = 'draft...';
+ const DRAFT_PLURAL = 'drafts...';
+ const SAVED_MESSAGE = 'All changes saved';
Polymer({
is: 'gr-diff-comment',
@@ -109,6 +115,11 @@
type: Boolean,
observer: '_toggleResolved',
},
+
+ _numPendingDiffRequests: {
+ type: Object,
+ value: {number: 0}, // Intentional to share the object across instances.
+ },
},
observers: [
@@ -424,13 +435,52 @@
});
},
+ _getSavingMessage(numPending) {
+ if (numPending === 0) { return SAVED_MESSAGE; }
+ return [
+ SAVING_MESSAGE,
+ numPending,
+ numPending === 1 ? DRAFT_SINGULAR : DRAFT_PLURAL,
+ ].join(' ');
+ },
+
+ _showStartRequest() {
+ const numPending = ++this._numPendingDiffRequests.number;
+ this._updateRequestToast(numPending);
+ },
+
+ _showEndRequest() {
+ const numPending = --this._numPendingDiffRequests.number;
+ this._updateRequestToast(numPending);
+ },
+
+ _updateRequestToast(numPending) {
+ const message = this._getSavingMessage(numPending);
+ this.debounce('draft-toast', () => {
+ // Note: the event is fired on the body rather than this element because
+ // this element may not be attached by the time this executes, in which
+ // case the event would not bubble.
+ document.body.dispatchEvent(new CustomEvent('show-alert',
+ {detail: {message}, bubbles: true}));
+ }, TOAST_DEBOUNCE_INTERVAL);
+ },
+
_saveDraft(draft) {
- return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
+ this._showStartRequest();
+ return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft)
+ .then(result => {
+ this._showEndRequest();
+ return result;
+ });
},
_deleteDraft(draft) {
+ this._showStartRequest();
return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
- draft);
+ draft).then(result => {
+ this._showEndRequest();
+ return result;
+ });
},
_getPatchNum() {
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 bdbf4f9..2816647 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
@@ -602,5 +602,34 @@
element.comment = {unresolved: true};
assert.isFalse(element.$$('.resolve input').checked);
});
+
+ suite('draft saving messages', () => {
+ test('_getSavingMessage', () => {
+ assert.equal(element._getSavingMessage(0), 'All changes saved');
+ assert.equal(element._getSavingMessage(1), 'Saving 1 draft...');
+ assert.equal(element._getSavingMessage(2), 'Saving 2 drafts...');
+ assert.equal(element._getSavingMessage(3), 'Saving 3 drafts...');
+ });
+
+ test('_show{Start,End}Request', () => {
+ const updateStub = sandbox.stub(element, '_updateRequestToast');
+ element._numPendingDiffRequests.number = 1;
+
+ element._showStartRequest();
+ assert.isTrue(updateStub.calledOnce);
+ assert.equal(updateStub.lastCall.args[0], 2);
+ assert.equal(element._numPendingDiffRequests.number, 2);
+
+ element._showEndRequest();
+ assert.isTrue(updateStub.calledTwice);
+ assert.equal(updateStub.lastCall.args[0], 1);
+ assert.equal(element._numPendingDiffRequests.number, 1);
+
+ element._showEndRequest();
+ assert.isTrue(updateStub.calledThrice);
+ assert.equal(updateStub.lastCall.args[0], 0);
+ assert.equal(element._numPendingDiffRequests.number, 0);
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 5c56826..c3332b3 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -29,6 +29,8 @@
const GERRIT_DOCS_BASE_URL = 'https://gerrit-review.googlesource.com/' +
'Documentation';
const GERRIT_DOCS_FILTER_PATH = '/user-notify.html';
+ const ABSOLUTE_URL_PATTERN = /^https?:/;
+ const TRAILING_SLASH_PATTERN = /\/$/;
Polymer({
is: 'gr-settings-view',
@@ -366,9 +368,13 @@
_getFilterDocsLink(docsBaseUrl) {
let base = docsBaseUrl;
- if (!base || !base.startsWith('http')) {
+ if (!base || !ABSOLUTE_URL_PATTERN.test(base)) {
base = GERRIT_DOCS_BASE_URL;
}
+
+ // Remove any trailing slash, since it is in the GERRIT_DOCS_FILTER_PATH.
+ base = base.replace(TRAILING_SLASH_PATTERN, '');
+
return base + GERRIT_DOCS_FILTER_PATH;
},
});
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index a8585da..e1182c1 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -386,6 +386,39 @@
assert.isTrue(element.$.emailEditor.loadData.calledOnce);
});
+ suite('_getFilterDocsLink', () => {
+ test('with http: docs base URL', () => {
+ const base = 'http://example.com/';
+ const result = element._getFilterDocsLink(base);
+ assert.equal(result, 'http://example.com/user-notify.html');
+ });
+
+ test('with http: docs base URL without slash', () => {
+ const base = 'http://example.com';
+ const result = element._getFilterDocsLink(base);
+ assert.equal(result, 'http://example.com/user-notify.html');
+ });
+
+ test('with https: docs base URL', () => {
+ const base = 'https://example.com/';
+ const result = element._getFilterDocsLink(base);
+ assert.equal(result, 'https://example.com/user-notify.html');
+ });
+
+ test('without docs base URL', () => {
+ const result = element._getFilterDocsLink(null);
+ assert.equal(result, 'https://gerrit-review.googlesource.com/' +
+ 'Documentation/user-notify.html');
+ });
+
+ test('ignores non HTTP links', () => {
+ const base = 'javascript://alert("evil");';
+ const result = element._getFilterDocsLink(base);
+ assert.equal(result, 'https://gerrit-review.googlesource.com/' +
+ 'Documentation/user-notify.html');
+ });
+ });
+
suite('when email verification token is provided', () => {
let resolveConfirm;