Merge "Add REST endpoint to reindex a single account"
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 4ac8d62..3a55b48 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -164,24 +164,24 @@
Documentation]
[[avatars-external]]
-=== avatars/external
+=== avatars-external
This plugin allows to use an external url to load the avatar images
from.
-link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/external[
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars-external[
Project] |
-link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/about.md[
+link:https://gerrit.googlesource.com/plugins/avatars-external/+doc/master/src/main/resources/Documentation/about.md[
Documentation] |
-link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/config.md[
+link:https://gerrit.googlesource.com/plugins/avatars-external/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
[[avatars-gravatar]]
-=== avatars/gravatar
+=== avatars-gravatar
Plugin to display user icons from Gravatar.
-link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/gravatar[
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars-gravatar[
Project]
[[branch-network]]
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 e7557fd..5a6c229 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
@@ -2420,6 +2420,65 @@
}
@Test
+ public void checkLabelsForMergedChangeWithNonAuthorCodeReview()
+ throws Exception {
+ // Configure Non-Author-Code-Review
+ RevCommit oldHead = getRemoteHead();
+ GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
+ testRepo.reset("config");
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent(), testRepo,
+ "Configure Non-Author-Code-Review",
+ "rules.pl",
+ "submit_rule(S) :-\n"
+ + " gerrit:default_submit(X),\n"
+ + " X =.. [submit | Ls],\n"
+ + " add_non_author_approval(Ls, R),\n"
+ + " S =.. [submit | R].\n"
+ + "\n"
+ + "add_non_author_approval(S1, S2) :-\n"
+ + " gerrit:commit_author(A),\n"
+ + " gerrit:commit_label(label('Code-Review', 2), R),\n"
+ + " R \\= A, !,\n"
+ + " S2 = [label('Non-Author-Code-Review', ok(R)) | S1].\n"
+ + "add_non_author_approval(S1,"
+ + " [label('Non-Author-Code-Review', need(_)) | S1]).");
+ push2.to(RefNames.REFS_CONFIG);
+ testRepo.reset(oldHead);
+
+ // Allow user to approve
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ AccountGroup.UUID registeredUsers =
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
+ String heads = RefNames.REFS_HEADS + "*";
+ Util.allow(cfg, Permission.forLabel(Util.codeReview().getName()), -2, 2,
+ registeredUsers, heads);
+ saveProjectConfig(project, cfg);
+
+ PushOneCommit.Result r = createChange();
+
+ setApiUser(user);
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .review(ReviewInput.approve());
+
+ setApiUser(admin);
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .submit();
+
+ ChangeInfo change = gApi.changes()
+ .id(r.getChangeId())
+ .get();
+ assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+ assertThat(change.labels.keySet()).containsExactly("Code-Review",
+ "Non-Author-Code-Review");
+ assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
+ assertPermitted(change, "Code-Review", 0, 1, 2);
+ }
+
+ @Test
public void checkLabelsForAutoClosedChange() throws Exception {
PushOneCommit.Result r = createChange();
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index e2883ffc..762b868 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -112,7 +112,7 @@
mappingBuilder.addString(name);
} else {
throw new IllegalArgumentException(
- "Unsupported filed type " + fieldType.getName());
+ "Unsupported field type " + fieldType.getName());
}
}
MappingProperties mapping = mappingBuilder.build();
@@ -151,7 +151,11 @@
private static <T> List<T> decodeProtos(JsonObject doc, String fieldName,
ProtobufCodec<T> codec) {
- return FluentIterable.from(doc.getAsJsonArray(fieldName))
+ JsonArray field = doc.getAsJsonArray(fieldName);
+ if (field == null) {
+ return null;
+ }
+ return FluentIterable.from(field)
.transform(i -> codec.decode(decodeBase64(i.toString())))
.toList();
}
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 629ad97..a545cad 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
@@ -163,6 +163,8 @@
/**
* Delete the assignee of a change.
+ *
+ * @return the assignee that was deleted, or null if there was no assignee.
*/
AccountInfo deleteAssignee() throws RestApiException;
diff --git a/gerrit-gwtexpui/BUILD b/gerrit-gwtexpui/BUILD
index d74fc8b..610a10b 100644
--- a/gerrit-gwtexpui/BUILD
+++ b/gerrit-gwtexpui/BUILD
@@ -16,7 +16,7 @@
deps = [
':SafeHtml',
':UserAgent',
- '//lib/gwt:user',
+ '//lib/gwt:user-neverlink',
],
visibility = ['//visibility:public'],
data = [
diff --git a/gerrit-gwtui-common/BUILD b/gerrit-gwtui-common/BUILD
index c6ad882..ac31856 100644
--- a/gerrit-gwtui-common/BUILD
+++ b/gerrit-gwtui-common/BUILD
@@ -10,7 +10,7 @@
'//gerrit-gwtexpui:SafeHtml',
'//gerrit-gwtexpui:UserAgent',
]
-DEPS = ['//lib/gwt:user']
+DEPS = ['//lib/gwt:user-neverlink']
SRC = 'src/main/java/com/google/gerrit/'
gwt_module(
diff --git a/gerrit-plugin-gwtui/BUILD b/gerrit-plugin-gwtui/BUILD
index 18bc038..01d9385 100644
--- a/gerrit-plugin-gwtui/BUILD
+++ b/gerrit-plugin-gwtui/BUILD
@@ -18,7 +18,21 @@
srcs = SRCS,
resources = glob(['src/main/**/*']),
exported_deps = ['//gerrit-gwtui-common:client-lib'],
- deps = DEPS + ['//lib/gwt:dev'],
+ deps = DEPS + [
+ '//gerrit-common:libclient-src.jar',
+ '//gerrit-extension-api:libclient-src.jar',
+ '//gerrit-gwtexpui:libClippy-src.jar',
+ '//gerrit-gwtexpui:libGlobalKey-src.jar',
+ '//gerrit-gwtexpui:libProgress-src.jar',
+ '//gerrit-gwtexpui:libSafeHtml-src.jar',
+ '//gerrit-gwtexpui:libUserAgent-src.jar',
+ '//gerrit-gwtui-common:libclient-src.jar',
+ '//gerrit-patch-jgit:libclient-src.jar',
+ '//gerrit-patch-jgit:libEdit-src.jar',
+ '//gerrit-prettify:libclient-src.jar',
+ '//gerrit-reviewdb:libclient-src.jar',
+ '//lib/gwt:dev-neverlink',
+ ],
)
java_library2(
diff --git a/gerrit-prettify/BUILD b/gerrit-prettify/BUILD
index b8d4dd6..c21a6f4 100644
--- a/gerrit-prettify/BUILD
+++ b/gerrit-prettify/BUILD
@@ -8,7 +8,7 @@
SRC + 'common/**/*.java',
]),
gwt_xml = SRC + 'PrettyFormatter.gwt.xml',
- deps = ['//lib/gwt:user'],
+ deps = ['//lib/gwt:user-neverlink'],
exported_deps = [
'//gerrit-extension-api:client',
'//gerrit-gwtexpui:SafeHtml',
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountSshKey.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountSshKey.java
index f63c618..78aef91 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountSshKey.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountSshKey.java
@@ -67,7 +67,7 @@
public AccountSshKey(final AccountSshKey.Id i, final String pub) {
id = i;
- sshPublicKey = pub;
+ sshPublicKey = pub.replace("\n", "").replace("\r", "");
valid = id.isValid();
}
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountSshKeyTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountSshKeyTest.java
index 139d360..07c00b9 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountSshKeyTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountSshKeyTest.java
@@ -25,6 +25,12 @@
+ "vf8IZixgjCmiBhaL2gt3wff6pP+NXJpTSA4aeWE5DfNK5tZlxlSxqkKOS8JRSUeNQov5T"
+ "w== john.doe@example.com";
+ private static final String KEY_WITH_NEWLINES =
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCgug5VyMXQGnem2H1KVC4/HcRcD4zzBqS\n"
+ + "uJBRWVonSSoz3RoAZ7bWXCVVGwchtXwUURD689wFYdiPecOrWOUgeeyRq754YWRhU+W28\n"
+ + "vf8IZixgjCmiBhaL2gt3wff6pP+NXJpTSA4aeWE5DfNK5tZlxlSxqkKOS8JRSUeNQov5T\n"
+ + "w== john.doe@example.com";
+
private final Account.Id accountId = new Account.Id(1);
@Test
@@ -47,4 +53,14 @@
assertThat(key.getEncodedKey()).isEqualTo(KEY.split(" ")[1]);
assertThat(key.getComment()).isEqualTo(KEY.split(" ")[2]);
}
+
+ @Test
+ public void testKeyWithNewLines() throws Exception {
+ AccountSshKey key = new AccountSshKey(
+ new AccountSshKey.Id(accountId, 1), KEY_WITH_NEWLINES);
+ assertThat(key.getSshPublicKey()).isEqualTo(KEY);
+ assertThat(key.getAlgorithm()).isEqualTo(KEY.split(" ")[0]);
+ assertThat(key.getEncodedKey()).isEqualTo(KEY.split(" ")[1]);
+ assertThat(key.getComment()).isEqualTo(KEY.split(" ")[2]);
+ }
}
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 8f25e43..cc6f5db 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
@@ -14,18 +14,21 @@
package com.google.gerrit.server;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import com.google.auto.value.AutoValue;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -61,6 +64,8 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
@@ -99,6 +104,25 @@
}
}
+ @AutoValue
+ public abstract static class StarRef {
+ private static final StarRef MISSING =
+ new AutoValue_StarredChangesUtil_StarRef(null, ImmutableSortedSet.of());
+
+ private static StarRef create(Ref ref, Iterable<String> labels) {
+ return new AutoValue_StarredChangesUtil_StarRef(
+ checkNotNull(ref),
+ ImmutableSortedSet.copyOf(labels));
+ }
+
+ @Nullable public abstract Ref ref();
+ public abstract ImmutableSortedSet<String> labels();
+
+ public ObjectId objectId() {
+ return ref() != null ? ref().getObjectId() : ObjectId.zeroId();
+ }
+ }
+
public static class IllegalLabelException extends IllegalArgumentException {
private static final long serialVersionUID = 1L;
@@ -153,8 +177,8 @@
public ImmutableSortedSet<String> getLabels(Account.Id accountId,
Change.Id changeId) throws OrmException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return ImmutableSortedSet.copyOf(
- readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
+ return readLabels(repo, RefNames.refsStarredChanges(changeId, accountId))
+ .labels();
} catch (IOException e) {
throw new OrmException(
String.format("Reading stars from change %d for account %d failed",
@@ -167,9 +191,9 @@
Set<String> labelsToRemove) throws OrmException {
try (Repository repo = repoManager.openRepository(allUsers)) {
String refName = RefNames.refsStarredChanges(changeId, accountId);
- ObjectId oldObjectId = getObjectId(repo, refName);
+ StarRef old = readLabels(repo, refName);
- SortedSet<String> labels = readLabels(repo, oldObjectId);
+ Set<String> labels = new HashSet<>(old.labels());
if (labelsToAdd != null) {
labels.addAll(labelsToAdd);
}
@@ -178,10 +202,10 @@
}
if (labels.isEmpty()) {
- deleteRef(repo, refName, oldObjectId);
+ deleteRef(repo, refName, old.objectId());
} else {
checkMutuallyExclusiveLabels(labels);
- updateLabels(repo, refName, oldObjectId, labels);
+ updateLabels(repo, refName, old.objectId(), labels);
}
indexer.index(dbProvider.get(), project, changeId);
@@ -222,11 +246,11 @@
}
}
- public ImmutableMultimap<Account.Id, String> byChange(Change.Id changeId)
+ public ImmutableMap<Account.Id, StarRef> byChange(Change.Id changeId)
throws OrmException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- ImmutableMultimap.Builder<Account.Id, String> builder =
- new ImmutableMultimap.Builder<>();
+ ImmutableMap.Builder<Account.Id, StarRef> builder =
+ ImmutableMap.builder();
for (String refPart : getRefNames(repo,
RefNames.refsStarredChangesPrefix(changeId))) {
Integer id = Ints.tryParse(refPart);
@@ -234,7 +258,7 @@
continue;
}
Account.Id accountId = new Account.Id(id);
- builder.putAll(accountId,
+ builder.put(accountId,
readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
}
return builder.build();
@@ -280,7 +304,7 @@
Account.Id accountId, String label) {
try {
return readLabels(repo,
- RefNames.refsStarredChanges(changeId, accountId))
+ RefNames.refsStarredChanges(changeId, accountId)).labels()
.contains(label);
} catch (IOException e) {
log.error(String.format(
@@ -311,8 +335,8 @@
public ObjectId getObjectId(Account.Id accountId, Change.Id changeId) {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return getObjectId(repo,
- RefNames.refsStarredChanges(changeId, accountId));
+ Ref ref = repo.exactRef(RefNames.refsStarredChanges(changeId, accountId));
+ return ref != null ? ref.getObjectId() : ObjectId.zeroId();
} catch (IOException e) {
log.error(String.format(
"Getting star object ID for account %d on change %d failed",
@@ -321,39 +345,33 @@
}
}
- private static ObjectId getObjectId(Repository repo, String refName)
+ private static StarRef readLabels(Repository repo, String refName)
throws IOException {
Ref ref = repo.exactRef(refName);
- return ref != null ? ref.getObjectId() : ObjectId.zeroId();
- }
-
- private static SortedSet<String> readLabels(Repository repo, String refName)
- throws IOException {
- return readLabels(repo, getObjectId(repo, refName));
- }
-
- private static TreeSet<String> readLabels(Repository repo, ObjectId id)
- throws IOException {
- if (ObjectId.zeroId().equals(id)) {
- return new TreeSet<>();
+ if (ref == null) {
+ return StarRef.MISSING;
}
try (ObjectReader reader = repo.newObjectReader()) {
- ObjectLoader obj = reader.open(id, Constants.OBJ_BLOB);
- TreeSet<String> labels = new TreeSet<>();
- Iterables.addAll(labels,
+ ObjectLoader obj = reader.open(ref.getObjectId(), Constants.OBJ_BLOB);
+ return StarRef.create(
+ ref,
Splitter.on(CharMatcher.whitespace()).omitEmptyStrings()
.split(new String(obj.getCachedBytes(Integer.MAX_VALUE), UTF_8)));
- return labels;
}
}
- public static ObjectId writeLabels(Repository repo, SortedSet<String> labels)
+ public static ObjectId writeLabels(Repository repo, Collection<String> labels)
throws IOException {
validateLabels(labels);
try (ObjectInserter oi = repo.newObjectInserter()) {
- ObjectId id = oi.insert(Constants.OBJ_BLOB,
- Joiner.on("\n").join(labels).getBytes(UTF_8));
+ ObjectId id = oi.insert(
+ Constants.OBJ_BLOB,
+ labels.stream()
+ .sorted()
+ .distinct()
+ .collect(joining("\n"))
+ .getBytes(UTF_8));
oi.flush();
return id;
}
@@ -366,7 +384,7 @@
}
}
- private static void validateLabels(Set<String> labels) {
+ private static void validateLabels(Collection<String> labels) {
if (labels == null) {
return;
}
@@ -383,7 +401,7 @@
}
private void updateLabels(Repository repo, String refName,
- ObjectId oldObjectId, SortedSet<String> labels)
+ ObjectId oldObjectId, Collection<String> labels)
throws IOException, OrmException {
try (RevWalk rw = new RevWalk(repo)) {
RefUpdate u = repo.updateRef(refName);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 3518ccd..3f3afa5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -36,7 +36,6 @@
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
import static com.google.gerrit.server.CommonConverters.toGitPerson;
-
import static java.util.stream.Collectors.toList;
import com.google.auto.value.AutoValue;
@@ -822,8 +821,10 @@
}
if (detailed) {
- labels.entrySet().stream().forEach(
- e -> setLabelValues(labelTypes.byLabel(e.getKey()), e.getValue()));
+ labels.entrySet().stream()
+ .filter(e -> labelTypes.byLabel(e.getKey()) != null)
+ .forEach(e -> setLabelValues(labelTypes.byLabel(e.getKey()),
+ e.getValue()));
}
for (Account.Id accountId : allUsers) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
index f07ee25..c5343e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
@@ -76,10 +76,10 @@
Op op = new Op();
bu.addOp(rsrc.getChange().getId(), op);
bu.execute();
- if (op.getDeletedAssignee() == null) {
- return Response.none();
- }
- return Response.ok(AccountJson.toAccountInfo(op.getDeletedAssignee()));
+ Account deletedAssignee = op.getDeletedAssignee();
+ return deletedAssignee == null
+ ? Response.none()
+ : Response.ok(AccountJson.toAccountInfo(deletedAssignee));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
index b8224a8..2a32652 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -26,13 +26,13 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index a7ab082..7ece10f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -238,9 +238,9 @@
* @throws MergeConflictException the rebase failed due to a merge conflict.
* @throws IOException the merge failed for another reason.
*/
- private RevCommit rebaseCommit(RepoContext ctx, RevCommit original,
- ObjectId base, String commitMessage)
- throws ResourceConflictException, MergeConflictException, IOException {
+ private RevCommit rebaseCommit(
+ RepoContext ctx, RevCommit original, ObjectId base, String commitMessage)
+ throws ResourceConflictException, IOException {
RevCommit parentCommit = original.getParent(0);
if (base.equals(parentCommit)) {
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 54361cc..795bec8 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
@@ -84,6 +84,7 @@
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -697,7 +698,7 @@
rw.markStart(mergeTip);
for (RevCommit c : alreadyAccepted) {
// If branch was not created by this submit.
- if (c != mergeTip) {
+ if (!Objects.equals(c, mergeTip)) {
rw.markUninteresting(c);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 5ef548c..f881f2e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -153,9 +153,7 @@
*/
public CheckedFuture<?, IOException> indexAsync(Project.NameKey project,
Change.Id id) {
- return executor != null
- ? submit(new IndexTask(project, id))
- : Futures.<Object, IOException> immediateCheckedFuture(null);
+ return submit(new IndexTask(project, id));
}
/**
@@ -235,9 +233,7 @@
* @return future for the deleting task.
*/
public CheckedFuture<?, IOException> deleteAsync(Change.Id id) {
- return executor != null
- ? submit(new DeleteTask(id))
- : Futures.<Object, IOException> immediateCheckedFuture(null);
+ return submit(new DeleteTask(id));
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentFormatter.java
new file mode 100644
index 0000000..9363722
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentFormatter.java
@@ -0,0 +1,188 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail.send;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import com.google.gerrit.common.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CommentFormatter {
+ public enum BlockType {
+ LIST,
+ PARAGRAPH,
+ PRE_FORMATTED,
+ QUOTE
+ }
+
+ public static class Block {
+ public BlockType type;
+ public String text;
+ public List<String> items;
+ }
+
+ /**
+ * Take a string of comment text that was written using the wiki-Like format
+ * and emit a list of blocks that can be rendered to block-level HTML. This
+ * method does not escape HTML.
+ *
+ * Adapted from the {@code wikify} method found in:
+ * com.google.gwtexpui.safehtml.client.SafeHtml
+ *
+ * @param source The raw, unescaped comment in the Gerrit wiki-like format.
+ * @return List of block objects, each with unescaped comment content.
+ */
+ public static List<Block> parse(@Nullable String source) {
+ if (isNullOrEmpty(source)) {
+ return Collections.emptyList();
+ }
+
+ List<Block> result = new ArrayList<>();
+ for (String p : source.split("\n\n")) {
+ if (isQuote(p)) {
+ result.add(makeQuote(p));
+ } else if (isPreFormat(p)) {
+ result.add(makePre(p));
+ } else if (isList(p)) {
+ makeList(p, result);
+ } else if (!p.isEmpty()) {
+ result.add(makeParagraph(p));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Take a block of comment text that contains a list and potentially
+ * paragraphs (but does not contain blank lines), generate appropriate block
+ * elements and append them to the output list.
+ *
+ * In simple cases, this will generate a single list block. For example, on
+ * the following input.
+ *
+ * * Item one.
+ * * Item two.
+ * * item three.
+ *
+ * However, if the list is adjacent to a paragraph, it will need to also
+ * generate that paragraph. Consider the following input.
+ *
+ * A bit of text describing the context of the list:
+ * * List item one.
+ * * List item two.
+ * * Et cetera.
+ *
+ * In this case, {@code makeList} generates a paragraph block object
+ * containing the non-bullet-prefixed text, followed by a list block.
+ *
+ * Adapted from the {@code wikifyList} method found in:
+ * com.google.gwtexpui.safehtml.client.SafeHtml
+ *
+ * @param p The block containing the list (as well as potential paragraphs).
+ * @param out The list of blocks to append to.
+ */
+ private static void makeList(String p, List<Block> out) {
+ Block block = null;
+ StringBuilder textBuilder = null;
+ boolean inList = false;
+ boolean inParagraph = false;
+
+ for (String line : p.split("\n")) {
+ if (line.startsWith("-") || line.startsWith("*")) {
+ // The next line looks like a list item. If not building a list already,
+ // then create one. Remove the list item marker (* or -) from the line.
+ if (!inList) {
+ if (inParagraph) {
+ // Add the finished paragraph block to the result.
+ inParagraph = false;
+ block.text = textBuilder.toString();
+ out.add(block);
+ }
+
+ inList = true;
+ block = new Block();
+ block.type = BlockType.LIST;
+ block.items = new ArrayList<>();
+ }
+ line = line.substring(1).trim();
+
+ } else if (!inList) {
+ // Otherwise, if a list has not yet been started, but the next line does
+ // not look like a list item, then add the line to a paragraph block. If
+ // a paragraph block has not yet been started, then create one.
+ if (!inParagraph) {
+ inParagraph = true;
+ block = new Block();
+ block.type = BlockType.PARAGRAPH;
+ textBuilder = new StringBuilder();
+ } else {
+ textBuilder.append(" ");
+ }
+ textBuilder.append(line);
+ continue;
+ }
+
+ block.items.add(line);
+ }
+
+ if (block != null) {
+ out.add(block);
+ }
+ }
+
+ private static Block makeQuote(String p) {
+ if (p.startsWith("> ")) {
+ p = p.substring(2);
+ } else if (p.startsWith(" > ")) {
+ p = p.substring(3);
+ }
+
+ Block block = new Block();
+ block.type = BlockType.QUOTE;
+ block.text = p.replaceAll("\n\\s?>\\s", "\n").trim();
+ return block;
+ }
+
+ private static Block makePre(String p) {
+ Block block = new Block();
+ block.type = BlockType.PRE_FORMATTED;
+ block.text = p;
+ return block;
+ }
+
+ private static Block makeParagraph(String p) {
+ Block block = new Block();
+ block.type = BlockType.PARAGRAPH;
+ block.text = p;
+ return block;
+ }
+
+ private static boolean isQuote(String p) {
+ return p.startsWith("> ") || p.startsWith(" > ");
+ }
+
+ private static boolean isPreFormat(String p) {
+ return p.startsWith(" ") || p.startsWith("\t")
+ || p.contains("\n ") || p.contains("\n\t");
+ }
+
+ private static boolean isList(String p) {
+ return p.startsWith("- ") || p.startsWith("* ")
+ || p.contains("\n- ") || p.contains("\n* ");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
index 9f72d66..5910aed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -52,6 +52,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
/** Send comments, after the author of them hit used Publish Comments in the UI.
*/
@@ -480,6 +481,7 @@
Map<String, Object> commentData = new HashMap<>();
commentData.put("lines", getLinesOfComment(comment, group.fileData));
commentData.put("message", comment.message.trim());
+ commentData.put("messageBlocks", formatComment(comment.message));
// Set the prefix.
String prefix = getCommentLinePrefix(comment);
@@ -533,6 +535,34 @@
return commentGroups;
}
+ private List<Map<String, Object>> formatComment(String comment) {
+ return CommentFormatter.parse(comment)
+ .stream()
+ .map(b -> {
+ Map<String, Object> map = new HashMap<>();
+ switch (b.type) {
+ case PARAGRAPH:
+ map.put("type", "paragraph");
+ map.put("text", b.text);
+ break;
+ case PRE_FORMATTED:
+ map.put("type", "pre");
+ map.put("text", b.text);
+ break;
+ case QUOTE:
+ map.put("type", "quote");
+ map.put("text", b.text);
+ break;
+ case LIST:
+ map.put("type", "list");
+ map.put("items", b.items);
+ break;
+ }
+ return map;
+ })
+ .collect(Collectors.toList());
+ }
+
private Repository getRepository() {
try {
return args.server.openRepository(projectState.getProject().getNameKey());
@@ -548,6 +578,7 @@
soyContext.put("commentFiles", getCommentGroupsTemplateData(repo));
}
soyContext.put("commentTimestamp", getCommentTimestamp());
+ soyContext.put("coverLetterBlocks", formatComment(getCoverLetter()));
}
private String getLine(PatchFile fileInfo, short side, int lineNbr) {
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 e0545a3..b9871ab 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
@@ -40,6 +40,10 @@
public class ListPlugins implements RestReadView<TopLevelResource> {
private final PluginLoader pluginLoader;
+ @Deprecated
+ @Option(name = "--format", usage = "(deprecated) output format")
+ private OutputFormat format = OutputFormat.TEXT;
+
@Option(name = "--all", aliases = {"-a"}, usage = "List all plugins, including disabled plugins")
private boolean all;
@@ -48,12 +52,23 @@
this.pluginLoader = pluginLoader;
}
+ public OutputFormat getFormat() {
+ return format;
+ }
+
+ public ListPlugins setFormat(OutputFormat fmt) {
+ this.format = fmt;
+ return this;
+ }
+
@Override
public Object apply(TopLevelResource resource) {
+ format = OutputFormat.JSON;
return display(null);
}
public JsonElement display(PrintWriter stdout) {
+ Map<String, PluginInfo> output = new TreeMap<>();
List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins(all));
Collections.sort(plugins, new Comparator<Plugin>() {
@Override
@@ -62,24 +77,30 @@
}
});
- if (stdout == null) {
- Map<String, PluginInfo> output = new TreeMap<>();
- for (Plugin p : plugins) {
- PluginInfo info = new PluginInfo(p);
+ if (!format.isJson()) {
+ stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
+ stdout.print("-------------------------------------------------------------------------------\n");
+ }
+
+ for (Plugin p : plugins) {
+ PluginInfo info = new PluginInfo(p);
+ if (format.isJson()) {
output.put(p.getName(), info);
+ } else {
+ stdout.format("%-30s %-10s %-8s %s\n", p.getName(),
+ Strings.nullToEmpty(info.version),
+ p.isDisabled() ? "DISABLED" : "ENABLED",
+ p.getSrcFile().getFileName());
}
+ }
+
+ if (stdout == null) {
return OutputFormat.JSON.newGson().toJsonTree(
output,
new TypeToken<Map<String, Object>>() {}.getType());
- }
- stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
- stdout.print("-------------------------------------------------------------------------------\n");
- for (Plugin p : plugins) {
- PluginInfo info = new PluginInfo(p);
- stdout.format("%-30s %-10s %-8s %s\n", p.getName(),
- Strings.nullToEmpty(info.version),
- p.isDisabled() ? "DISABLED" : "ENABLED",
- p.getSrcFile().getFileName());
+ } else if (format.isJson()) {
+ format.newGson().toJson(output,
+ new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
stdout.print('\n');
}
stdout.flush();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 21896f2..913cffd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -23,6 +23,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
@@ -51,6 +52,7 @@
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.StarredChangesUtil.StarRef;
import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
@@ -352,6 +354,7 @@
@Deprecated
private Set<Account.Id> starredByUser;
private ImmutableMultimap<Account.Id, String> stars;
+ private ImmutableMap<Account.Id, StarRef> starRefs;
private ReviewerSet reviewers;
private List<ReviewerStatusUpdate> reviewerUpdates;
private PersonIdent author;
@@ -1204,7 +1207,12 @@
if (!lazyLoad) {
return ImmutableMultimap.of();
}
- stars = checkNotNull(starredChangesUtil).byChange(legacyId);
+ ImmutableMultimap.Builder<Account.Id, String> b =
+ ImmutableMultimap.builder();
+ for (Map.Entry<Account.Id, StarRef> e : starRefs().entrySet()) {
+ b.putAll(e.getKey(), e.getValue().labels());
+ }
+ return b.build();
}
return stars;
}
@@ -1213,6 +1221,16 @@
this.stars = ImmutableMultimap.copyOf(stars);
}
+ public ImmutableMap<Account.Id, StarRef> starRefs() throws OrmException {
+ if (starRefs == null) {
+ if (!lazyLoad) {
+ return ImmutableMap.of();
+ }
+ starRefs = checkNotNull(starredChangesUtil).byChange(legacyId);
+ }
+ return starRefs;
+ }
+
@AutoValue
abstract static class ReviewedByEvent {
private static ReviewedByEvent create(ChangeMessage msg) {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 21be41b..75492d6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -19,6 +19,7 @@
/**
* @param commentFiles
* @param coverLetter
+ * @param coverLetterBlocks
* @param email
* @param fromName
*/
@@ -33,10 +34,6 @@
padding: 0 10px;
{/let}
- {let $messageStyle kind="css"}
- white-space: pre-wrap;
- {/let}
-
{let $ulStyle kind="css"}
list-style: none;
padding-left: 20px;
@@ -53,7 +50,7 @@
{/if}
{if $coverLetter}
- <div style="white-space:pre-wrap">{$coverLetter}</div>
+ {call .WikiFormat}{param content: $coverLetterBlocks /}{/call}
{/if}
<ul style="{$ulStyle}">
@@ -114,9 +111,7 @@
</p>
{/if}
- <p style="{$messageStyle}">
- {$comment.message}
- </p>
+ {call .WikiFormat}{param content: $comment.messageBlocks /}{/call}
</li>
{/foreach}
</ul>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
index 88cd8d0..a256a06 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
@@ -41,3 +41,40 @@
<pre style="{$preStyle}">{$content}</pre>
{/template}
+/**
+ * Take a list of unescaped comment blocks and emit safely escaped HTML to
+ * render it nicely with wiki-like format.
+ *
+ * Each block is a map with a type key. When the type is 'paragraph', 'quote',
+ * or 'pre', it also has a 'text' key that maps to the unescaped text content
+ * for the block. If the type is 'list', the map will have a 'items' key which
+ * maps to list of unescaped list item strings.
+ *
+ * This mechanism encodes as little structure as possible in order to depend on
+ * the Soy autoescape mechanism for all of the content.
+ *
+ * @param content
+ */
+{template .WikiFormat private="true" autoescape="strict" kind="html"}
+ {let $blockquoteStyle kind="css"}
+ border-left: 1px solid #aaa;
+ margin: 10px 0;
+ padding: 0 10px;
+ {/let}
+
+ {foreach $block in $content}
+ {if $block.type == 'paragraph'}
+ <p>{$block.text}</p>
+ {elseif $block.type == 'quote'}
+ <blockquote style="{$blockquoteStyle}">{$block.text}</blockquote>
+ {elseif $block.type == 'pre'}
+ {call .Pre}{param content: $block.text /}{/call}
+ {elseif $block.type == 'list'}
+ <ul>
+ {foreach $item in $block.items}
+ <li>{$item}</li>
+ {/foreach}
+ </ul>
+ {/if}
+ {/foreach}
+{/template}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentFormatterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentFormatterTest.java
new file mode 100644
index 0000000..8a51c94
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentFormatterTest.java
@@ -0,0 +1,388 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail.send;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.LIST;
+import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.PARAGRAPH;
+import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.PRE_FORMATTED;
+import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.QUOTE;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class CommentFormatterTest {
+ private void assertBlock(List<CommentFormatter.Block> list, int index,
+ CommentFormatter.BlockType type, String text) {
+ CommentFormatter.Block block = list.get(index);
+ assertThat(block.type).isEqualTo(type);
+ assertThat(block.text).isEqualTo(text);
+ assertThat(block.items).isNull();
+ }
+
+ private void assertListBlock(List<CommentFormatter.Block> list, int index,
+ int itemIndex, String text) {
+ CommentFormatter.Block block = list.get(index);
+ assertThat(block.type).isEqualTo(LIST);
+ assertThat(block.items.get(itemIndex)).isEqualTo(text);
+ assertThat(block.text).isNull();
+ }
+
+ @Test
+ public void testParseNullAsEmpty() {
+ assertThat(CommentFormatter.parse(null)).isEmpty();
+ }
+
+ @Test
+ public void testParseEmpty() {
+ assertThat(CommentFormatter.parse("")).isEmpty();
+ }
+
+ @Test
+ public void testParseSimple() {
+ String comment = "Para1";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PARAGRAPH, comment);
+ }
+
+ @Test
+ public void testParseMultilinePara() {
+ String comment = "Para 1\nStill para 1";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PARAGRAPH, comment);
+ }
+
+ @Test
+ public void testParseParaBreak() {
+ String comment = "Para 1\n\nPara 2\n\nPara 3";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "Para 1");
+ assertBlock(result, 1, PARAGRAPH, "Para 2");
+ assertBlock(result, 2, PARAGRAPH, "Para 3");
+ }
+
+ @Test
+ public void testParseQuote() {
+ String comment = "> Quote text";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, QUOTE, "Quote text");
+ }
+
+ @Test
+ public void testParseExcludesEmpty() {
+ String comment = "Para 1\n\n\n\nPara 2";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "Para 1");
+ assertBlock(result, 1, PARAGRAPH, "Para 2");
+ }
+
+ @Test
+ public void testParseQuoteLeadSpace() {
+ String comment = " > Quote text";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, QUOTE, "Quote text");
+ }
+
+ @Test
+ public void testParseMultiLineQuote() {
+ String comment = "> Quote line 1\n> Quote line 2\n > Quote line 3\n";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, QUOTE, "Quote line 1\nQuote line 2\nQuote line 3");
+ }
+
+ @Test
+ public void testParsePre() {
+ String comment = " Four space indent.";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PRE_FORMATTED, comment);
+ }
+
+ @Test
+ public void testParseOneSpacePre() {
+ String comment = " One space indent.\n Another line.";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PRE_FORMATTED, comment);
+ }
+
+ @Test
+ public void testParseTabPre() {
+ String comment = "\tOne tab indent.\n\tAnother line.\n Yet another!";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PRE_FORMATTED, comment);
+ }
+
+ @Test
+ public void testParseIntermediateLeadingWhitespacePre() {
+ String comment = "No indent.\n\tNonzero indent.\nNo indent again.";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertBlock(result, 0, PRE_FORMATTED, comment);
+ }
+
+ @Test
+ public void testParseStarList() {
+ String comment = "* Item 1\n* Item 2\n* Item 3";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertListBlock(result, 0, 0, "Item 1");
+ assertListBlock(result, 0, 1, "Item 2");
+ assertListBlock(result, 0, 2, "Item 3");
+ }
+
+ @Test
+ public void testParseDashList() {
+ String comment = "- Item 1\n- Item 2\n- Item 3";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertListBlock(result, 0, 0, "Item 1");
+ assertListBlock(result, 0, 1, "Item 2");
+ assertListBlock(result, 0, 2, "Item 3");
+ }
+
+ @Test
+ public void testParseMixedList() {
+ String comment = "- Item 1\n* Item 2\n- Item 3\n* Item 4";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+ assertListBlock(result, 0, 0, "Item 1");
+ assertListBlock(result, 0, 1, "Item 2");
+ assertListBlock(result, 0, 2, "Item 3");
+ assertListBlock(result, 0, 3, "Item 4");
+ }
+
+ @Test
+ public void testParseMixedBlockTypes() {
+ String comment = "Paragraph\nacross\na\nfew\nlines."
+ + "\n\n"
+ + "> Quote\n> across\n> not many lines."
+ + "\n\n"
+ + "Another paragraph"
+ + "\n\n"
+ + "* Series\n* of\n* list\n* items"
+ + "\n\n"
+ + "Yet another paragraph"
+ + "\n\n"
+ + "\tPreformatted text."
+ + "\n\n"
+ + "Parting words.";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(7);
+ assertBlock(result, 0, PARAGRAPH, "Paragraph\nacross\na\nfew\nlines.");
+ assertBlock(result, 1, QUOTE, "Quote\nacross\nnot many lines.");
+ assertBlock(result, 2, PARAGRAPH, "Another paragraph");
+ assertListBlock(result, 3, 0, "Series");
+ assertListBlock(result, 3, 1, "of");
+ assertListBlock(result, 3, 2, "list");
+ assertListBlock(result, 3, 3, "items");
+ assertBlock(result, 4, PARAGRAPH, "Yet another paragraph");
+ assertBlock(result, 5, PRE_FORMATTED, "\tPreformatted text.");
+ assertBlock(result, 6, PARAGRAPH, "Parting words.");
+ }
+
+ @Test
+ public void testBulletList1() {
+ String comment = "A\n\n* line 1\n* 2nd line";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertListBlock(result, 1, 0, "line 1");
+ assertListBlock(result, 1, 1, "2nd line");
+ }
+
+ @Test
+ public void testBulletList2() {
+ String comment = "A\n\n* line 1\n* 2nd line\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertListBlock(result, 1, 0, "line 1");
+ assertListBlock(result, 1, 1, "2nd line");
+ assertBlock(result, 2, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testBulletList3() {
+ String comment = "* line 1\n* 2nd line\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertListBlock(result, 0, 0, "line 1");
+ assertListBlock(result, 0, 1, "2nd line");
+ assertBlock(result, 1, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testBulletList4() {
+ String comment = "To see this bug, you have to:\n" //
+ + "* Be on IMAP or EAS (not on POP)\n"//
+ + "* Be very unlucky\n";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "To see this bug, you have to:");
+ assertListBlock(result, 1, 0, "Be on IMAP or EAS (not on POP)");
+ assertListBlock(result, 1, 1, "Be very unlucky");
+ }
+
+ @Test
+ public void testBulletList5() {
+ String comment = "To see this bug,\n" //
+ + "you have to:\n" //
+ + "* Be on IMAP or EAS (not on POP)\n"//
+ + "* Be very unlucky\n";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "To see this bug, you have to:");
+ assertListBlock(result, 1, 0, "Be on IMAP or EAS (not on POP)");
+ assertListBlock(result, 1, 1, "Be very unlucky");
+ }
+
+ @Test
+ public void testDashList1() {
+ String comment = "A\n\n- line 1\n- 2nd line";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertListBlock(result, 1, 0, "line 1");
+ assertListBlock(result, 1, 1, "2nd line");
+ }
+
+ @Test
+ public void testDashList2() {
+ String comment = "A\n\n- line 1\n- 2nd line\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertListBlock(result, 1, 0, "line 1");
+ assertListBlock(result, 1, 1, "2nd line");
+ assertBlock(result, 2, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testDashList3() {
+ String comment = "- line 1\n- 2nd line\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertListBlock(result, 0, 0, "line 1");
+ assertListBlock(result, 0, 1, "2nd line");
+ assertBlock(result, 1, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testPreformat1() {
+ String comment = "A\n\n This is pre\n formatted";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertBlock(result, 1, PRE_FORMATTED, " This is pre\n formatted");
+ }
+
+ @Test
+ public void testPreformat2() {
+ String comment = "A\n\n This is pre\n formatted\n\nbut this is not";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertBlock(result, 1, PRE_FORMATTED, " This is pre\n formatted");
+ assertBlock(result, 2, PARAGRAPH, "but this is not");
+ }
+
+ @Test
+ public void testPreformat3() {
+ String comment = "A\n\n Q\n <R>\n S\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "A");
+ assertBlock(result, 1, PRE_FORMATTED, " Q\n <R>\n S");
+ assertBlock(result, 2, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testPreformat4() {
+ String comment = " Q\n <R>\n S\n\nB";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, PRE_FORMATTED, " Q\n <R>\n S");
+ assertBlock(result, 1, PARAGRAPH, "B");
+ }
+
+ @Test
+ public void testQuote1() {
+ String comment = "> I'm happy\n > with quotes!\n\nSee above.";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(2);
+ assertBlock(result, 0, QUOTE, "I'm happy\nwith quotes!");
+ assertBlock(result, 1, PARAGRAPH, "See above.");
+ }
+
+ @Test
+ public void testQuote2() {
+ String comment = "See this said:\n\n > a quoted\n > string block\n\nOK?";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(3);
+ assertBlock(result, 0, PARAGRAPH, "See this said:");
+ assertBlock(result, 1, QUOTE, "a quoted\nstring block");
+ assertBlock(result, 2, PARAGRAPH, "OK?");
+ }
+
+ @Test
+ public void testNestedQuotes1() {
+ String comment = " > > prior\n > \n > next\n";
+ List<CommentFormatter.Block> result = CommentFormatter.parse(comment);
+
+ assertThat(result).hasSize(1);
+
+ // Note: block does not encode nesting.
+ assertBlock(result, 0, QUOTE, "> prior\n\nnext");
+ }
+}
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 a1a5fbc..5899e11 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
@@ -62,6 +62,7 @@
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.change.ChangeField;
@@ -87,6 +88,7 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
@@ -119,6 +121,7 @@
@Inject protected ChangeQueryBuilder queryBuilder;
@Inject protected GerritApi gApi;
@Inject protected IdentifiedUser.GenericFactory userFactory;
+ @Inject protected ChangeEditModifier changeEditModifier;
@Inject protected ChangeIndexCollection indexes;
@Inject protected ChangeIndexer indexer;
@Inject protected InMemoryDatabase schemaFactory;
@@ -1496,6 +1499,35 @@
}
@Test
+ public void hasEdit() throws Exception {
+ Account.Id user1 = createAccount("user1");
+ Account.Id user2 = createAccount("user2");
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo));
+ PatchSet ps1 = db.patchSets().get(change1.currentPatchSetId());
+ Change change2 = insert(repo, newChange(repo));
+ PatchSet ps2 = db.patchSets().get(change2.currentPatchSetId());
+
+ requestContext.setContext(newRequestContext(user1));
+ assertQuery("has:edit");
+ assertThat(changeEditModifier.createEdit(change1, ps1))
+ .isEqualTo(RefUpdate.Result.NEW);
+ assertThat(changeEditModifier.createEdit(change2, ps2))
+ .isEqualTo(RefUpdate.Result.NEW);
+
+ requestContext.setContext(newRequestContext(user2));
+ assertQuery("has:edit");
+ assertThat(changeEditModifier.createEdit(change2, ps2))
+ .isEqualTo(RefUpdate.Result.NEW);
+
+ requestContext.setContext(newRequestContext(user1));
+ assertQuery("has:edit", change2, change1);
+
+ requestContext.setContext(newRequestContext(user2));
+ assertQuery("has:edit", change2);
+ }
+
+ @Test
public void byCommitsOnBranchNotMerged() throws Exception {
TestRepository<Repo> repo = createProject("repo");
int n = 10;
diff --git a/lib/gwt/BUILD b/lib/gwt/BUILD
index da6f8eb..116f86c 100644
--- a/lib/gwt/BUILD
+++ b/lib/gwt/BUILD
@@ -23,6 +23,14 @@
)
java_library(
+ name = 'dev-neverlink',
+ exports = ['@dev//jar'],
+ visibility = ['//visibility:public'],
+ neverlink = 1,
+ data = ['//lib:LICENSE-Apache2.0'],
+)
+
+java_library(
name = 'javax-validation_src',
exports = ['@javax_validation//src'],
visibility = ['//visibility:public'],
diff --git a/plugins/BUILD b/plugins/BUILD
index 4af46ac..27690c8 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -1,17 +1,9 @@
load('//tools/bzl:genrule2.bzl', 'genrule2')
-
-CORE = [
- 'commit-message-length-validator',
- 'download-commands',
- 'hooks',
- 'replication',
- 'reviewnotes',
- 'singleusergroup'
-]
+load('//tools/bzl:plugins.bzl', 'CORE_PLUGINS')
genrule2(
name = 'core',
- srcs = ['//plugins/%s:%s.jar' % (n, n) for n in CORE],
+ srcs = ['//plugins/%s:%s.jar' % (n, n) for n in CORE_PLUGINS],
cmd = 'mkdir -p $$TMP/WEB-INF/plugins;' +
'for s in $(SRCS) ; do ' +
'ln -s $$ROOT/$$s $$TMP/WEB-INF/plugins;done;' +
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index eea302a..747336d 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit eea302a7dd069bc6fd84e09c10ddecebdb6301e1
+Subproject commit 747336da4bcca8000badf6e4ed05ada536645b16
diff --git a/plugins/replication b/plugins/replication
index 531ed17..bb1ee89 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 531ed177aaab613c5830b7578df2dd4d84a7f319
+Subproject commit bb1ee89bdf6bdd2dfdf5d16a11a18b077c7c523d
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index 3066589..3b17756 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -91,6 +91,7 @@
_handleRemove: function(e) {
var toRemove = e.detail.account;
this._removeAccount(toRemove);
+ this.$.entry.focus();
},
_removeAccount: function(toRemove) {
@@ -105,7 +106,6 @@
}
if (matches) {
this.splice('accounts', i, 1);
- this.$.entry.focus();
return;
}
}
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 fdc3b23..a1b7852 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
@@ -177,7 +177,7 @@
}
gr-reply-dialog {
min-width: initial;
- width: 90vw;
+ width: 100vw;
}
.downloadContainer {
display: none;
@@ -213,6 +213,9 @@
flex: initial;
margin-right: 0;
}
+ .scrollable {
+ @apply(--layout-scroll);
+ }
}
</style>
<div class="container loading" hidden$="[[!_loading]]">Loading...</div>
@@ -348,6 +351,7 @@
on-close="_handleDownloadDialogClose"></gr-download-dialog>
</gr-overlay>
<gr-overlay id="replyOverlay"
+ class="scrollable"
no-cancel-on-outside-click
on-iron-overlay-opened="_handleReplyOverlayOpen"
with-backdrop>
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 4e68559..3142733 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
@@ -21,6 +21,7 @@
<link rel="import" href="../../diff/gr-diff/gr-diff.html">
<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
@@ -64,7 +65,7 @@
.row:not(.header):hover {
background-color: #f5fafd;
}
- .row[selected] {
+ .row.selected {
background-color: #ebf5fb;
}
.path {
@@ -145,7 +146,7 @@
max-width: 25em;
}
@media screen and (max-width: 50em) {
- .row[selected] {
+ .row.selected {
background-color: transparent;
}
.stats {
@@ -207,7 +208,7 @@
items="[[_shownFiles]]"
as="file"
initial-count="[[_fileListIncrement]]">
- <div class="row" selected$="[[_computeFileSelected(index, selectedIndex)]]">
+ <div class="file-row row" selected$="[[_computeFileSelected(index, selectedIndex)]]">
<div class="reviewed" hidden$="[[!_loggedIn]]" hidden>
<input type="checkbox" checked$="[[_computeReviewed(file, _reviewed)]]"
data-path$="[[file.__path]]" on-change="_handleReviewedChange"
@@ -233,8 +234,17 @@
[[_computeCommentsString(comments, patchRange.patchNum, file.__path)]]
</div>
<div class$="[[_computeClass('stats', file.__path)]]">
- <span class="added">+[[file.lines_inserted]]</span>
- <span class="removed">-[[file.lines_deleted]]</span>
+ <span class="added" hidden$=[[file.binary]]>
+ +[[file.lines_inserted]]
+ </span>
+ <span class="removed" hidden$=[[file.binary]]>
+ -[[file.lines_deleted]]
+ </span>
+ <span class$="[[_computeBinaryClass(file.size_delta)]]"
+ hidden$=[[!file.binary]]>
+ [[_formatBytes(file.size_delta)]]
+ [[_formatPercentage(file.size, file.size_delta)]]
+ </span>
</div>
<div class="show-hide">
<label class="show-hide">
@@ -263,6 +273,20 @@
<span class="removed">-[[_patchChange.deleted]]</span>
</div>
</div>
+ <div class="row totalChanges">
+ <div class="total-stats" hidden$="[[_hideBinaryChangeTotals]]">
+ <span class="added">
+ [[_formatBytes(_patchChange.size_delta_inserted)]]
+ [[_formatPercentage(_patchChange.total_size,
+ _patchChange.size_delta_inserted)]]
+ </span>
+ <span class="removed">
+ [[_formatBytes(_patchChange.size_delta_deleted)]]
+ [[_formatPercentage(_patchChange.total_size,
+ _patchChange.size_delta_deleted)]]
+ </span>
+ </div>
+ </div>
<gr-button
class="fileListButton"
id="incrementButton"
@@ -279,7 +303,11 @@
</gr-button>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
- <gr-diff-cursor id="cursor"></gr-diff-cursor>
+ <gr-diff-cursor id="diffCursor"></gr-diff-cursor>
+ <gr-cursor-manager
+ id="fileCursor"
+ scroll-behavior="keep-visible"
+ cursor-target-class="selected"></gr-cursor-manager>
</template>
<script src="gr-file-list.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index ab889ac..b423f50 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -35,10 +35,6 @@
drafts: Object,
revisions: Object,
projectConfig: Object,
- selectedIndex: {
- type: Number,
- notify: true,
- },
keyEventTarget: {
type: Object,
value: function() { return document.body; },
@@ -83,6 +79,10 @@
type: Boolean,
computed: '_shouldHideChangeTotals(_patchChange)',
},
+ _hideBinaryChangeTotals: {
+ type: Boolean,
+ computed: '_shouldHideBinaryChangeTotals(_patchChange)',
+ },
_shownFiles: {
type: Array,
computed: '_computeFilesShown(_numFilesShown, _files.*)',
@@ -174,12 +174,21 @@
return filesNoCommitMsg.reduce(function(acc, obj) {
var inserted = obj.lines_inserted ? obj.lines_inserted : 0;
var deleted = obj.lines_deleted ? obj.lines_deleted : 0;
+ var total_size = (obj.size && obj.binary) ? obj.size : 0;
+ var size_delta_inserted =
+ obj.binary && obj.size_delta > 0 ? obj.size_delta : 0;
+ var size_delta_deleted =
+ obj.binary && obj.size_delta < 0 ? obj.size_delta : 0;
return {
inserted: acc.inserted + inserted,
deleted: acc.deleted + deleted,
+ size_delta_inserted: acc.size_delta_inserted + size_delta_inserted,
+ size_delta_deleted: acc.size_delta_deleted + size_delta_deleted,
+ total_size: acc.total_size + total_size,
};
- }, {inserted: 0, deleted: 0});
+ }, {inserted: 0, deleted: 0, size_delta_inserted: 0,
+ size_delta_deleted: 0, total_size: 0});
},
_getDiffPreferences: function() {
@@ -240,7 +249,7 @@
this.set(['_shownFiles', i, '__expanded'], false);
this.set(['_files', i, '__expanded'], false);
}
- this.$.cursor.handleDiffUpdate();
+ this.$.diffCursor.handleDiffUpdate();
},
_computeCommentsString: function(comments, patchNum, path) {
@@ -313,7 +322,7 @@
if (!this._showInlineDiffs) { return; }
e.preventDefault();
- this.$.cursor.moveLeft();
+ this.$.diffCursor.moveLeft();
},
_handleShiftRightKey: function(e) {
@@ -321,20 +330,20 @@
if (!this._showInlineDiffs) { return; }
e.preventDefault();
- this.$.cursor.moveRight();
+ this.$.diffCursor.moveRight();
},
_handleIKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- if (this.selectedIndex === undefined) { return; }
+ if (this.$.fileCursor.index === -1) { return; }
e.preventDefault();
- var expanded = this._files[this.selectedIndex].__expanded;
+ var expanded = this._files[this.$.fileCursor.index].__expanded;
// Until Polymer 2.0, manual management of reflection between _files
// and _shownFiles is necessary.
- this.set(['_shownFiles', this.selectedIndex, '__expanded'],
+ this.set(['_shownFiles', this.$.fileCursor.index, '__expanded'],
!expanded);
- this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
+ this.set(['_files', this.$.fileCursor.index, '__expanded'], !expanded);
},
_handleCapitalIKey: function(e) {
@@ -346,14 +355,11 @@
_handleDownKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
-
e.preventDefault();
if (this._showInlineDiffs) {
- this.$.cursor.moveDown();
+ this.$.diffCursor.moveDown();
} else {
- this.selectedIndex =
- Math.min(this._numFilesShown, this.selectedIndex + 1);
- this._scrollToSelectedFile();
+ this.$.fileCursor.next();
}
},
@@ -362,10 +368,9 @@
e.preventDefault();
if (this._showInlineDiffs) {
- this.$.cursor.moveUp();
+ this.$.diffCursor.moveUp();
} else {
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
- this._scrollToSelectedFile();
+ this.$.fileCursor.previous();
}
},
@@ -412,9 +417,9 @@
e.preventDefault();
if (e.shiftKey) {
- this.$.cursor.moveToNextCommentThread();
+ this.$.diffCursor.moveToNextCommentThread();
} else {
- this.$.cursor.moveToNextChunk();
+ this.$.diffCursor.moveToNextChunk();
}
},
@@ -424,9 +429,9 @@
e.preventDefault();
if (e.shiftKey) {
- this.$.cursor.moveToPreviousCommentThread();
+ this.$.diffCursor.moveToPreviousCommentThread();
} else {
- this.$.cursor.moveToPreviousChunk();
+ this.$.diffCursor.moveToPreviousChunk();
}
},
@@ -448,45 +453,34 @@
},
_openCursorFile: function() {
- var diff = this.$.cursor.getTargetDiffElement();
+ var diff = this.$.diffCursor.getTargetDiffElement();
page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
diff.path));
},
_openSelectedFile: function(opt_index) {
if (opt_index != null) {
- this.selectedIndex = opt_index;
+ this.$.fileCursor.setCursorAtIndex(opt_index);
}
page.show(this._computeDiffURL(this.changeNum, this.patchRange,
- this._files[this.selectedIndex].__path));
+ this._files[this.$.fileCursor.index].__path));
},
_addDraftAtTarget: function() {
- var diff = this.$.cursor.getTargetDiffElement();
- var target = this.$.cursor.getTargetLineElement();
+ var diff = this.$.diffCursor.getTargetDiffElement();
+ var target = this.$.diffCursor.getTargetLineElement();
if (diff && target) {
diff.addDraftAtLine(target);
}
},
- _scrollToSelectedFile: function() {
- var el = this.$$('.row[selected]');
- var top = 0;
- for (var node = el; node; node = node.offsetParent) {
- top += node.offsetTop;
- }
-
- // Don't scroll if it's already in view.
- if (top > window.pageYOffset &&
- top < window.pageYOffset + window.innerHeight - el.clientHeight) {
- return;
- }
-
- window.scrollTo(0, top - document.body.clientHeight / 2);
+ _shouldHideChangeTotals: function(_patchChange) {
+ return _patchChange.inserted === 0 && _patchChange.deleted === 0;
},
- _shouldHideChangeTotals: function(_patchChange) {
- return (_patchChange.inserted === 0 && _patchChange.deleted === 0);
+ _shouldHideBinaryChangeTotals: function(_patchChange) {
+ return _patchChange.size_delta_inserted === 0 &&
+ _patchChange.size_delta_deleted === 0;
},
_computeFileSelected: function(index, selectedIndex) {
@@ -512,6 +506,31 @@
return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
},
+ _formatBytes: function(bytes) {
+ if (bytes == 0) return '+/-0 B';
+ var bits = 1024;
+ var decimals = 1;
+ var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
+ var exponent = Math.floor(Math.log(Math.abs(bytes)) / Math.log(bits));
+ var prepend = bytes > 0 ? '+' : '';
+ return prepend + parseFloat((bytes / Math.pow(bits, exponent))
+ .toFixed(decimals)) + ' ' + sizes[exponent];
+ },
+
+ _formatPercentage: function(size, delta) {
+ var oldSize = size - delta;
+
+ if (oldSize === 0) { return ''; }
+
+ var percentage = Math.round(Math.abs(delta * 100 / oldSize));
+ return '(' + (delta > 0 ? '+' : '-') + percentage + '%)';
+ },
+
+ _computeBinaryClass: function(delta) {
+ if (delta === 0) { return; }
+ return delta >= 0 ? 'added' : 'removed';
+ },
+
_computeClass: function(baseClass, path) {
var classes = [baseClass];
if (path === COMMIT_MESSAGE_PATH) {
@@ -533,8 +552,12 @@
var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
// Overwrite the cursor's list of diffs:
- this.$.cursor.splice.apply(this.$.cursor,
- ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
+ this.$.diffCursor.splice.apply(this.$.diffCursor,
+ ['diffs', 0, this.$.diffCursor.diffs.length].concat(diffElements));
+
+ var files = Polymer.dom(this.root).querySelectorAll('.file-row');
+ this.$.fileCursor.stops = files;
+ if (this.$.fileCursor.index === -1) { this.$.fileCursor.moveToStart(); }
}.bind(this), 1);
},
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 1e146b5..bc7c0c7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -103,10 +103,30 @@
test('calculate totals for patch number', function() {
element._files = [
{__path: '/COMMIT_MSG', lines_inserted: 9},
- {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
- {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
+ {
+ __path: 'file_added_in_rev2.txt',
+ lines_inserted: 1,
+ lines_deleted: 1,
+ size_delta: 10,
+ size: 100,
+ },
+ {
+ __path: 'myfile.txt',
+ lines_inserted: 1,
+ lines_deleted: 1,
+ size_delta: 10,
+ size: 100,
+ },
];
- assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+ assert.deepEqual(element._patchChange, {
+ inserted: 2,
+ deleted: 2,
+ size_delta_inserted: 0,
+ size_delta_deleted: 0,
+ total_size: 0,
+ });
+ assert.isTrue(element._hideBinaryChangeTotals);
+ assert.isFalse(element._hideChangeTotals);
// Test with a commit message that isn't the first file.
element._files = [
@@ -114,21 +134,137 @@
{__path: '/COMMIT_MSG', lines_inserted: 9},
{__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
];
- assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+ assert.deepEqual(element._patchChange, {
+ inserted: 2,
+ deleted: 2,
+ size_delta_inserted: 0,
+ size_delta_deleted: 0,
+ total_size: 0,
+ });
+ assert.isTrue(element._hideBinaryChangeTotals);
+ assert.isFalse(element._hideChangeTotals);
// Test with no commit message.
element._files = [
{__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
{__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
];
- assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+ assert.deepEqual(element._patchChange, {
+ inserted: 2,
+ deleted: 2,
+ size_delta_inserted: 0,
+ size_delta_deleted: 0,
+ total_size: 0,
+ });
+ assert.isTrue(element._hideBinaryChangeTotals);
+ assert.isFalse(element._hideChangeTotals);
// Test with files missing either lines_inserted or lines_deleted.
element._files = [
{__path: 'file_added_in_rev2.txt', lines_inserted: 1},
{__path: 'myfile.txt', lines_deleted: 1},
];
- assert.deepEqual(element._patchChange, {inserted: 1, deleted: 1});
+ assert.deepEqual(element._patchChange, {
+ inserted: 1,
+ deleted: 1,
+ size_delta_inserted: 0,
+ size_delta_deleted: 0,
+ total_size: 0,
+ });
+ assert.isTrue(element._hideBinaryChangeTotals);
+ assert.isFalse(element._hideChangeTotals);
+ });
+
+ test('binary only files', function() {
+ element._files = [
+ {__path: '/COMMIT_MSG', lines_inserted: 9},
+ {__path: 'file_binary', binary: true, size_delta: 10, size: 100},
+ {__path: 'file_binary', binary: true, size_delta: -5, size: 120},
+ ];
+ assert.deepEqual(element._patchChange, {
+ inserted: 0,
+ deleted: 0,
+ size_delta_inserted: 10,
+ size_delta_deleted: -5,
+ total_size: 220,
+ });
+ assert.isFalse(element._hideBinaryChangeTotals);
+ assert.isTrue(element._hideChangeTotals);
+ });
+
+ test('binary and regular files', function() {
+ element._files = [
+ {__path: '/COMMIT_MSG', lines_inserted: 9},
+ {__path: 'file_binary', binary: true, size_delta: 10, size: 100},
+ {__path: 'file_binary', binary: true, size_delta: -5, size: 120},
+ {__path: 'myfile.txt', lines_deleted: 5, size_delta: -10, size: 100},
+ {__path: 'myfile2.txt', lines_inserted: 10},
+ ];
+ assert.deepEqual(element._patchChange, {
+ inserted: 10,
+ deleted: 5,
+ size_delta_inserted: 10,
+ size_delta_deleted: -5,
+ total_size: 220,
+ });
+ assert.isFalse(element._hideBinaryChangeTotals);
+ assert.isFalse(element._hideChangeTotals);
+ });
+
+ test('_formatBytes function', function() {
+ var table = {
+ 64: '+64 B',
+ 1023: '+1023 B',
+ 1024: '+1 KiB',
+ 4096: '+4 KiB',
+ 1073741824: '+1 GiB',
+ '-64': '-64 B',
+ '-1023': '-1023 B',
+ '-1024': '-1 KiB',
+ '-4096': '-4 KiB',
+ '-1073741824': '-1 GiB',
+ 0: '+/-0 B',
+ };
+
+ for (var bytes in table) {
+ if (table.hasOwnProperty(bytes)) {
+ assert.equal(element._formatBytes(bytes), table[bytes]);
+ }
+ }
+ });
+
+ test('_formatPercentage function', function() {
+ var table = [
+ { size: 100,
+ delta: 100,
+ display: '',
+ },
+ { size: 195060,
+ delta: 64,
+ display: '(+0%)',
+ },
+ { size: 195060,
+ delta: -64,
+ display: '(-0%)',
+ },
+ { size: 394892,
+ delta: -7128,
+ display: '(-2%)',
+ },
+ { size: 90,
+ delta: -10,
+ display: '(-10%)',
+ },
+ { size: 110,
+ delta: 10,
+ display: '(+10%)',
+ },
+ ];
+
+ table.forEach(function(item) {
+ assert.equal(element._formatPercentage(
+ item.size, item.delta), item.display);
+ });
});
suite('keyboard shortcuts', function() {
@@ -143,7 +279,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
});
test('toggle left diff via shortcut', function() {
@@ -162,24 +298,26 @@
test('keyboard shortcuts', function() {
flushAsynchronousOperations();
- var elementItems = Polymer.dom(element.root).querySelectorAll(
- '.row:not(.header)');
- assert.equal(elementItems.length, 4);
- assert.isTrue(elementItems[0].hasAttribute('selected'));
- assert.isFalse(elementItems[1].hasAttribute('selected'));
- assert.isFalse(elementItems[2].hasAttribute('selected'));
+
+ var items = Polymer.dom(element.root).querySelectorAll('.file-row');
+ element.$.fileCursor.stops = items;
+ element.$.fileCursor.setCursorAtIndex(0);
+ assert.equal(items.length, 3);
+ assert.isTrue(items[0].classList.contains('selected'));
+ assert.isFalse(items[1].classList.contains('selected'));
+ assert.isFalse(items[2].classList.contains('selected'));
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
- assert.equal(element.selectedIndex, 1);
+ assert.equal(element.$.fileCursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sandbox.stub(page, 'show');
- assert.equal(element.selectedIndex, 2);
+ assert.equal(element.$.fileCursor.index, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
'Should navigate to /c/42/2/myfile.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
- assert.equal(element.selectedIndex, 1);
+ assert.equal(element.$.fileCursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element, 79, null, 'o');
assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
'Should navigate to /c/42/2/file_added_in_rev2.txt');
@@ -187,20 +325,20 @@
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
- assert.equal(element.selectedIndex, 0);
-
- showStub.restore();
+ assert.equal(element.$.fileCursor.index, 0);
});
test('i key shows/hides selected inline diff', function() {
- element.selectedIndex = 0;
+ flushAsynchronousOperations();
+ element.$.fileCursor.stops = element.diffs;
+ element.$.fileCursor.setCursorAtIndex(0);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[0].hasAttribute('hidden'));
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isTrue(element.diffs[0].hasAttribute('hidden'));
- element.selectedIndex = 1;
+ element.$.fileCursor.setCursorAtIndex(1);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[1].hasAttribute('hidden'));
@@ -280,7 +418,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var fileRows =
@@ -363,7 +501,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
@@ -401,7 +539,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var diffDisplay = element.diffs[0];
element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
@@ -444,7 +582,7 @@
patchNum: '2',
};
var computeSpy = sandbox.spy(element, '_fileListActionsVisible');
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
element._numFilesShown = 1;
flush(function() {
assert.isTrue(computeSpy.lastCall.returnValue);
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index 1afd548..3435547 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -76,6 +76,10 @@
.message {
max-width: 80ch;
}
+ .collapsed .message {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
.collapsed .name,
.collapsed .content,
.collapsed .message,
@@ -93,6 +97,7 @@
}
.collapsed .content {
flex: 1;
+ margin-right: .25em;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index d04a942..7c1759d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -48,7 +48,7 @@
on-tap="_handleExpandCollapseTap">
[[_computeExpandCollapseMessage(_expanded)]]
</gr-button>
- <div
+ <span
id="automatedMessageToggleContainer"
hidden$="[[!_hasAutomatedMessages(messages)]]">
/
@@ -56,7 +56,7 @@
on-tap="_handleAutomatedMessageToggleTap">
[[_computeAutomatedToggleText(_hideAutomated)]]
</gr-button>
- </div>
+ </span>
</div>
</div>
<template
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 39db8e4..9b7a11f0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -138,6 +138,14 @@
.action:visited {
color: #00e;
}
+ @media screen and (max-width: 50em) {
+ :host {
+ max-height: none;
+ }
+ .container {
+ max-height: none;
+ }
+ }
</style>
<div class="container">
<section class="peopleContainer">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index 051e7df..497cae4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -384,9 +384,6 @@
var text = line.text;
if (line.type !== GrDiffLine.Type.BLANK) {
td.classList.add('content');
- if (!text) {
- text = '\xa0';
- }
}
td.classList.add(line.type);
var html = util.escapeHTML(text);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
index c8eea3c..2d0786a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -21,7 +21,7 @@
<template>
<gr-cursor-manager
id="cursorManager"
- scroll="[[_scrollBehavior]]"
+ scroll-behavior="[[_scrollBehavior]]"
cursor-target-class="target-row"
target="{{diffRow}}"></gr-cursor-manager>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index 9bbcea5..1d2beab 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -42,7 +42,7 @@
<span class="text">
<span>[[account.name]]</span>
<span hidden$="[[!_computeShowEmail(showEmail, account)]]">
- ([[account.email]])
+ [[_computeEmailStr(account)]]
</span>
</span>
</span>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 40b7cf1..ffbb55b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -44,5 +44,12 @@
_computeShowEmail: function(showEmail, account) {
return !!(showEmail && account && account.email);
},
+
+ _computeEmailStr: function(account) {
+ if (account.name) {
+ return '(' + account.email + ')';
+ }
+ return account.email;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index f3d8861..65331d6 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -71,6 +71,9 @@
assert.equal(element._computeShowEmail(
false, undefined), false);
+
+ assert.equal(element._computeEmailStr({name: 'test', email: 'test'}), '(test)');
+ assert.equal(element._computeEmailStr({email: 'test'}, ''), 'test');
});
});
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 5de54bb..d7017ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -73,6 +73,7 @@
id="cursor"
index="{{_index}}"
cursor-target-class="selected"
+ scroll-behavior="keep-visible"
stops="[[_getSuggestionElems(_suggestions)]]"></gr-cursor-manager>
</template>
<script src="gr-autocomplete.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 0626288..863b85a 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -199,6 +199,7 @@
},
_handleInputKeydown: function(e) {
+ this._focused = true;
switch (e.keyCode) {
case 38: // Up
e.preventDefault();
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 7b3bc23..81f5186 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -59,7 +59,7 @@
* 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
* the viewport.
*/
- scroll: {
+ scrollBehavior: {
type: String,
value: ScrollBehavior.NEVER,
},
@@ -108,6 +108,10 @@
}
},
+ setCursorAtIndex: function(index) {
+ this.setCursor(this.stops[index]);
+ },
+
/**
* Move the cursor forward or backward by delta. Noop if moving past either
* end of the stop list.
@@ -194,7 +198,9 @@
},
_scrollToTarget: function() {
- if (!this.target || this.scroll === ScrollBehavior.NEVER) { return; }
+ if (!this.target || this.scrollBehavior === ScrollBehavior.NEVER) {
+ return;
+ }
// Calculate where the element is relative to the window.
var top = this.target.offsetTop;
@@ -204,7 +210,7 @@
top += offsetParent.offsetTop;
}
- if (this.scroll === ScrollBehavior.KEEP_VISIBLE &&
+ if (this.scrollBehavior === ScrollBehavior.KEEP_VISIBLE &&
top > window.pageYOffset &&
top < window.pageYOffset + window.innerHeight) { return; }
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 07ccf2b..a98b00b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -212,6 +212,8 @@
},
saveDiffPreferences: function(prefs, opt_errFn, opt_ctx) {
+ // Invalidate the cache.
+ this._cache['/accounts/self/preferences.diff'] = undefined;
return this.send('PUT', '/accounts/self/preferences.diff', prefs,
opt_errFn, opt_ctx);
},
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 392c320..d07968f 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -330,5 +330,14 @@
element.getChanges(1, null, 'n,z');
assert.equal(stub.args[0][3].S, 0);
});
+
+ test('saveDiffPreferences invalidates cache line', function() {
+ var cacheKey = '/accounts/self/preferences.diff';
+ var sendStub = sandbox.stub(element, 'send');
+ element._cache[cacheKey] = {tab_size: 4};
+ element.saveDiffPreferences({tab_size: 8});
+ assert.isTrue(sendStub.called);
+ assert.notOk(element._cache[cacheKey]);
+ });
});
</script>
diff --git a/tools/bzl/plugins.bzl b/tools/bzl/plugins.bzl
new file mode 100644
index 0000000..287a989
--- /dev/null
+++ b/tools/bzl/plugins.bzl
@@ -0,0 +1,12 @@
+CORE_PLUGINS = [
+ 'commit-message-length-validator',
+ 'download-commands',
+ 'hooks',
+ 'replication',
+ 'reviewnotes',
+ 'singleusergroup',
+]
+
+CUSTOM_PLUGINS = [
+ 'cookbook-plugin',
+]
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index ac55c72..41c89b1 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -1,5 +1,8 @@
load('//tools/bzl:pkg_war.bzl', 'LIBS', 'PGMLIBS')
load('//tools/bzl:classpath.bzl', 'classpath_collector')
+load('//tools/bzl:plugins.bzl',
+ 'CORE_PLUGINS',
+ 'CUSTOM_PLUGINS')
PROVIDED_DEPS = [
'//lib/bouncycastle:bcprov',
@@ -23,8 +26,6 @@
'//gerrit-main:main_lib',
'//gerrit-plugin-gwtui:gwtui-api-lib',
'//gerrit-server:server',
- # TODO(davido): figure out why it's not reached through test dependencies
- '//lib:jimfs',
'//lib/asciidoctor:asciidoc_lib',
'//lib/asciidoctor:doc_indexer_lib',
'//lib/auto:auto-value',
@@ -50,10 +51,10 @@
classpath_collector(
name = 'main_classpath_collect',
- deps = LIBS + PGMLIBS + DEPS + PROVIDED_DEPS,
+ deps = LIBS + PGMLIBS + DEPS + TEST_DEPS + PROVIDED_DEPS +
+ ['//plugins/%s:%s__plugin' % (n, n)
+ for n in CORE_PLUGINS + CUSTOM_PLUGINS],
testonly = 1,
- # TODO(davido): Handle plugins
- #scan_plugins(),
)
classpath_collector(
diff --git a/tools/eclipse/project_bzl.py b/tools/eclipse/project_bzl.py
index 1d07f8a..86fb527 100755
--- a/tools/eclipse/project_bzl.py
+++ b/tools/eclipse/project_bzl.py
@@ -158,6 +158,10 @@
p.endswith('prolog/libcommon.jar'):
lib.add(p)
else:
+ # Don't mess up with Bazel internal test runner dependencies.
+ # When we use Eclipse we rely on it for running the tests
+ if p.endswith("external/bazel_tools/tools/jdk/TestRunner_deploy.jar"):
+ continue
if p.startswith("external"):
p = path.join(ext, p)
lib.add(p)
@@ -169,9 +173,8 @@
# Exception: we need source here for GWT SDM mode to work
if p.endswith('libEdit.jar'):
p = p[:-4] + '-src.jar'
- assert path.exists(p), p
lib.add(p)
-
+
for s in sorted(src):
out = None
@@ -258,14 +261,14 @@
gen_classpath(ext_location)
gen_factorypath(ext_location)
gen_primary_build_tool()
-
+
# TODO(davido): Remove this when GWT gone
gwt_working_dir = ".gwt_work_dir"
if not path.isdir(gwt_working_dir):
makedirs(path.join(ROOT, gwt_working_dir))
try:
- check_call(['bazel', 'build', MAIN, GWT])
+ check_call(['bazel', 'build', MAIN, GWT, '//gerrit-patch-jgit:libEdit-src.jar'])
except CalledProcessError:
exit(1)
except KeyboardInterrupt: