Merge "Report low FPS to analytics"
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index d542a0b..40e022d 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -17,6 +17,7 @@
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
@@ -91,6 +92,7 @@
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
diff --git a/WORKSPACE b/WORKSPACE
index d482577..15d8651 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -680,8 +680,8 @@
maven_jar(
name = "junit",
- artifact = "junit:junit:4.11",
- sha1 = "4e031bb61df09069aeb2bffb4019e7a5034a4ee0",
+ artifact = "junit:junit:4.12",
+ sha1 = "2973d150c0dc1fefe998f834810d68f278ea58ec",
)
maven_jar(
@@ -697,18 +697,18 @@
sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
)
-TRUTH_VERS = "0.39"
+TRUTH_VERS = "0.40"
maven_jar(
name = "truth",
artifact = "com.google.truth:truth:" + TRUTH_VERS,
- sha1 = "bd1bf5706ff34eb7ff80fef8b0c4320f112ef899",
+ sha1 = "0d74e716afec045cc4a178dbbfde2a8314ae5574",
)
maven_jar(
name = "truth-java8-extension",
artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
- sha1 = "1499bc88cda9d674afb30da9813b44bcd4512d0d",
+ sha1 = "636e49d675bc28e0b3ae0edd077d6acbbb159166",
)
# When bumping the easymock version number, make sure to also move powermock to a compatible version
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 0a06c31..58a298e 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -15,9 +15,9 @@
package com.google.gerrit.elasticsearch;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.index.change.ChangeField.APPROVAL_CODEC;
-import static com.google.gerrit.server.index.change.ChangeField.CHANGE_CODEC;
-import static com.google.gerrit.server.index.change.ChangeField.PATCH_SET_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
import static java.nio.charset.StandardCharsets.UTF_8;
diff --git a/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java b/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
index b736262..84b6a04 100644
--- a/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
+++ b/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
@@ -14,9 +14,20 @@
package com.google.gerrit.extensions.auth.oauth;
-import java.io.Serializable;
+import static com.google.common.base.Preconditions.checkNotNull;
-/* OAuth token */
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * OAuth token.
+ *
+ * <p>Only implements {@link Serializable} for backwards compatibility; new extensions should not
+ * depend on the serialized format.
+ */
public class OAuthToken implements Serializable {
private static final long serialVersionUID = 1L;
@@ -32,8 +43,9 @@
private final long expiresAt;
/**
- * The identifier of the OAuth provider that issued this token in the form
- * <tt>"plugin-name:provider-name"</tt>, or {@code null}.
+ * The identifier of the OAuth provider that issued this token in the form {@code
+ * "plugin-name:provider-name"}, or {@code null}. The empty string {@code ""} is treated the same
+ * as {@code null}.
*/
private final String providerId;
@@ -41,12 +53,13 @@
this(token, secret, raw, Long.MAX_VALUE, null);
}
- public OAuthToken(String token, String secret, String raw, long expiresAt, String providerId) {
- this.token = token;
- this.secret = secret;
- this.raw = raw;
+ public OAuthToken(
+ String token, String secret, String raw, long expiresAt, @Nullable String providerId) {
+ this.token = checkNotNull(token, "token");
+ this.secret = checkNotNull(secret, "secret");
+ this.raw = checkNotNull(raw, "raw");
this.expiresAt = expiresAt;
- this.providerId = providerId;
+ this.providerId = Strings.emptyToNull(providerId);
}
public String getToken() {
@@ -69,7 +82,37 @@
return System.currentTimeMillis() > expiresAt;
}
+ @Nullable
public String getProviderId() {
return providerId;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof OAuthToken)) {
+ return false;
+ }
+ OAuthToken t = (OAuthToken) o;
+ return token.equals(t.token)
+ && secret.equals(t.secret)
+ && raw.equals(t.raw)
+ && expiresAt == t.expiresAt
+ && Objects.equals(providerId, t.providerId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(token, secret, raw, expiresAt, providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("token", token)
+ .add("secret", secret)
+ .add("raw", raw)
+ .add("expiresAt", expiresAt)
+ .add("providerId", providerId)
+ .toString();
+ }
}
diff --git a/java/com/google/gerrit/extensions/registration/DynamicMap.java b/java/com/google/gerrit/extensions/registration/DynamicMap.java
index e0db0c7..7178a16 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -83,6 +83,11 @@
binder.bind(key).toProvider(new DynamicMapProvider<>(member)).in(Scopes.SINGLETON);
}
+ /** Returns an empty DynamicMap instance * */
+ public static <T> DynamicMap<T> emptyMap() {
+ return new PrivateInternals_DynamicMapImpl<>();
+ }
+
final ConcurrentMap<NamePair, Provider<T>> items;
DynamicMap() {
@@ -188,8 +193,8 @@
private final String exportName;
NamePair(String pn, String en) {
- this.pluginName = pn;
- this.exportName = en;
+ pluginName = pn;
+ exportName = en;
}
@Override
@@ -206,8 +211,4 @@
return false;
}
}
-
- public static <T> DynamicMap<T> emptyMap() {
- return new DynamicMap<T>() {};
- }
}
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 5cdf267..7ffb86d 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -139,7 +139,7 @@
}
public DynamicSet() {
- this(Collections.<AtomicReference<Provider<T>>>emptySet());
+ this(Collections.emptySet());
}
@Override
diff --git a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
index 50aed7d..1973f70 100644
--- a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
+++ b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.registration;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.gerrit.extensions.annotations.Export;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -31,6 +33,7 @@
* @return handle to remove the item at a later point in time.
*/
public RegistrationHandle put(String pluginName, String exportName, Provider<T> item) {
+ checkNotNull(item);
final NamePair key = new NamePair(pluginName, exportName);
items.put(key, item);
return new RegistrationHandle() {
@@ -53,6 +56,7 @@
* the collection.
*/
public ReloadableRegistrationHandle<T> put(String pluginName, Key<T> key, Provider<T> item) {
+ checkNotNull(item);
String exportName = ((Export) key.getAnnotation()).value();
NamePair np = new NamePair(pluginName, exportName);
items.put(np, item);
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 468aa67..c8f8fff 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -16,11 +16,11 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
-import static com.google.gerrit.server.index.change.ChangeField.APPROVAL_CODEC;
-import static com.google.gerrit.server.index.change.ChangeField.CHANGE_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
-import static com.google.gerrit.server.index.change.ChangeField.PATCH_SET_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
diff --git a/java/com/google/gerrit/reviewdb/client/ChangeMessage.java b/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
index edc022f..8e397f0 100644
--- a/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
+++ b/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
@@ -149,6 +149,26 @@
}
@Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ChangeMessage)) {
+ return false;
+ }
+ ChangeMessage m = (ChangeMessage) o;
+ return Objects.equals(key, m.key)
+ && Objects.equals(author, m.author)
+ && Objects.equals(writtenOn, m.writtenOn)
+ && Objects.equals(message, m.message)
+ && Objects.equals(patchset, m.patchset)
+ && Objects.equals(tag, m.tag)
+ && Objects.equals(realAuthor, m.realAuthor);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, author, writtenOn, message, patchset, tag, realAuthor);
+ }
+
+ @Override
public String toString() {
return "ChangeMessage{"
+ "key="
diff --git a/java/com/google/gerrit/reviewdb/client/PatchSet.java b/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 4536b67..849fd75 100644
--- a/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/** A single revision of a {@link Change}. */
public final class PatchSet {
@@ -280,6 +281,26 @@
}
@Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PatchSet)) {
+ return false;
+ }
+ PatchSet p = (PatchSet) o;
+ return Objects.equals(id, p.id)
+ && Objects.equals(revision, p.revision)
+ && Objects.equals(uploader, p.uploader)
+ && Objects.equals(createdOn, p.createdOn)
+ && Objects.equals(groups, p.groups)
+ && Objects.equals(pushCertificate, p.pushCertificate)
+ && Objects.equals(description, p.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, revision, uploader, createdOn, groups, pushCertificate, description);
+ }
+
+ @Override
public String toString() {
return "[PatchSet " + getId().toString() + "]";
}
diff --git a/java/com/google/gerrit/reviewdb/server/ReviewDbCodecs.java b/java/com/google/gerrit/reviewdb/server/ReviewDbCodecs.java
new file mode 100644
index 0000000..631e7f5
--- /dev/null
+++ b/java/com/google/gerrit/reviewdb/server/ReviewDbCodecs.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.reviewdb.server;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+
+/** {@link ProtobufCodec} instances for ReviewDb types. */
+public class ReviewDbCodecs {
+ public static final ProtobufCodec<PatchSetApproval> APPROVAL_CODEC =
+ CodecFactory.encoder(PatchSetApproval.class);
+
+ public static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
+
+ public static final ProtobufCodec<PatchSet> PATCH_SET_CODEC =
+ CodecFactory.encoder(PatchSet.class);
+
+ private ReviewDbCodecs() {}
+}
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 75a9991..e635072 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -116,14 +116,6 @@
return notes.load().getChangeMessages();
}
- public Iterable<ChangeMessage> byPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId)
- throws OrmException {
- if (!migration.readChanges()) {
- return db.changeMessages().byPatchSet(psId);
- }
- return notes.load().getChangeMessagesByPatchSet().get(psId);
- }
-
public void addChangeMessage(ReviewDb db, ChangeUpdate update, ChangeMessage changeMessage)
throws OrmException {
checkState(
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 442bc2a..db8ea41 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -118,6 +118,8 @@
* AuthType#HTTP_LDAP}, and {@link AuthType#LDAP_BIND} usernames.
*
* <p>The name {@code gerrit:} was a very poor choice.
+ *
+ * <p>Scheme names must not contain colons (':').
*/
public static final String SCHEME_GERRIT = "gerrit";
@@ -140,6 +142,13 @@
public abstract static class Key implements Serializable {
private static final long serialVersionUID = 1L;
+ /**
+ * Creates an external ID key.
+ *
+ * @param scheme the scheme name, must not contain colons (':'), can be {@code null}
+ * @param id the external ID, must not contain colons (':')
+ * @return the created external ID key
+ */
public static Key create(@Nullable String scheme, String id) {
return new AutoValue_ExternalId_Key(Strings.emptyToNull(scheme), id);
}
@@ -198,10 +207,28 @@
}
}
+ /**
+ * Creates an external ID.
+ *
+ * @param scheme the scheme name, must not contain colons (':')
+ * @param id the external ID, must not contain colons (':')
+ * @param accountId the ID of the account to which the external ID belongs
+ * @return the created external ID
+ */
public static ExternalId create(String scheme, String id, Account.Id accountId) {
return create(Key.create(scheme, id), accountId, null, null);
}
+ /**
+ * Creates an external ID.
+ *
+ * @param scheme the scheme name, must not contain colons (':')
+ * @param id the external ID, must not contain colons (':')
+ * @param accountId the ID of the account to which the external ID belongs
+ * @param email the email of the external ID, may be {@code null}
+ * @param hashedPassword the hashed password of the external ID, may be {@code null}
+ * @return the created external ID
+ */
public static ExternalId create(
String scheme,
String id,
@@ -222,17 +249,35 @@
}
public static ExternalId createWithPassword(
- Key key, Account.Id accountId, @Nullable String email, String plainPassword) {
+ Key key, Account.Id accountId, @Nullable String email, @Nullable String plainPassword) {
plainPassword = Strings.emptyToNull(plainPassword);
String hashedPassword =
plainPassword != null ? HashedPassword.fromPassword(plainPassword).encode() : null;
return create(key, accountId, email, hashedPassword);
}
- public static ExternalId createUsername(String id, Account.Id accountId, String plainPassword) {
+ /**
+ * Create a external ID for a username (scheme "username").
+ *
+ * @param id the external ID, must not contain colons (':')
+ * @param accountId the ID of the account to which the external ID belongs
+ * @param plainPassword the plain HTTP password, may be {@code null}
+ * @return the created external ID
+ */
+ public static ExternalId createUsername(
+ String id, Account.Id accountId, @Nullable String plainPassword) {
return createWithPassword(Key.create(SCHEME_USERNAME, id), accountId, null, plainPassword);
}
+ /**
+ * Creates an external ID with an email.
+ *
+ * @param scheme the scheme name, must not contain colons (':')
+ * @param id the external ID, must not contain colons (':')
+ * @param accountId the ID of the account to which the external ID belongs
+ * @param email the email of the external ID, may be {@code null}
+ * @return the created external ID
+ */
public static ExternalId createWithEmail(
String scheme, String id, Account.Id accountId, @Nullable String email) {
return createWithEmail(Key.create(scheme, id), accountId, email);
diff --git a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
index 1ac3bca..f380051 100644
--- a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
+++ b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
@@ -16,16 +16,23 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.IntKeyCacheSerializer;
+import com.google.gerrit.server.cache.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
+import java.io.IOException;
@Singleton
public class OAuthTokenCache {
@@ -37,11 +44,47 @@
return new CacheModule() {
@Override
protected void configure() {
- persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class);
+ persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class)
+ .version(1)
+ .keySerializer(new IntKeyCacheSerializer<>(Account.Id::new))
+ .valueSerializer(new Serializer());
}
};
}
+ // Defined outside of OAuthToken class, since that is in the extensions package which doesn't have
+ // access to the serializer code.
+ @VisibleForTesting
+ static class Serializer implements CacheSerializer<OAuthToken> {
+ @Override
+ public byte[] serialize(OAuthToken object) {
+ return ProtoCacheSerializers.toByteArray(
+ OAuthTokenProto.newBuilder()
+ .setToken(object.getToken())
+ .setSecret(object.getSecret())
+ .setRaw(object.getRaw())
+ .setExpiresAt(object.getExpiresAt())
+ .setProviderId(Strings.nullToEmpty(object.getProviderId()))
+ .build());
+ }
+
+ @Override
+ public OAuthToken deserialize(byte[] in) {
+ OAuthTokenProto proto;
+ try {
+ proto = OAuthTokenProto.parseFrom(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("failed to deserialize OAuthToken");
+ }
+ return new OAuthToken(
+ proto.getToken(),
+ proto.getSecret(),
+ proto.getRaw(),
+ proto.getExpiresAt(),
+ Strings.emptyToNull(proto.getProviderId()));
+ }
+ }
+
private final Cache<Account.Id, OAuthToken> cache;
@Inject
diff --git a/java/com/google/gerrit/server/cache/BooleanCacheSerializer.java b/java/com/google/gerrit/server/cache/BooleanCacheSerializer.java
new file mode 100644
index 0000000..59fc946
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/BooleanCacheSerializer.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.protobuf.TextFormat;
+import java.util.Arrays;
+
+public enum BooleanCacheSerializer implements CacheSerializer<Boolean> {
+ INSTANCE;
+
+ private static final byte[] TRUE = Boolean.toString(true).getBytes(UTF_8);
+ private static final byte[] FALSE = Boolean.toString(false).getBytes(UTF_8);
+
+ @Override
+ public byte[] serialize(Boolean object) {
+ byte[] bytes = checkNotNull(object) ? TRUE : FALSE;
+ return Arrays.copyOf(bytes, bytes.length);
+ }
+
+ @Override
+ public Boolean deserialize(byte[] in) {
+ if (Arrays.equals(in, TRUE)) {
+ return Boolean.TRUE;
+ } else if (Arrays.equals(in, FALSE)) {
+ return Boolean.FALSE;
+ }
+ throw new IllegalArgumentException("Invalid Boolean value: " + TextFormat.escapeBytes(in));
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/CacheSerializer.java b/java/com/google/gerrit/server/cache/CacheSerializer.java
index 6bd1322..08deecd 100644
--- a/java/com/google/gerrit/server/cache/CacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/CacheSerializer.java
@@ -14,13 +14,29 @@
package com.google.gerrit.server.cache;
-import java.io.IOException;
-
-/** Interface for serializing/deserializing a type to/from a persistent cache. */
+/**
+ * Interface for serializing/deserializing a type to/from a persistent cache.
+ *
+ * <p>Implementations are null-hostile and will throw exceptions from {@link #serialize} when passed
+ * null values, unless otherwise specified.
+ */
public interface CacheSerializer<T> {
- /** Serializes the object to a new byte array. */
- byte[] serialize(T object) throws IOException;
+ /**
+ * Serializes the object to a new byte array.
+ *
+ * @param object object to serialize.
+ * @return serialized byte array representation.
+ * @throws RuntimeException for malformed input, for example null or an otherwise unsupported
+ * value.
+ */
+ byte[] serialize(T object);
- /** Deserializes a single object from the given byte array. */
- T deserialize(byte[] in) throws IOException;
+ /**
+ * Deserializes a single object from the given byte array.
+ *
+ * @param in serialized byte array representation.
+ * @throws RuntimeException for malformed input, for example null or an otherwise corrupt
+ * serialized representation.
+ */
+ T deserialize(byte[] in);
}
diff --git a/java/com/google/gerrit/server/cache/EnumCacheSerializer.java b/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
index 6ea6121..c5be783 100644
--- a/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
@@ -14,28 +14,26 @@
package com.google.gerrit.server.cache;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.base.Converter;
import com.google.common.base.Enums;
-import java.io.IOException;
public class EnumCacheSerializer<E extends Enum<E>> implements CacheSerializer<E> {
- private final Class<E> clazz;
+ private final Converter<String, E> converter;
public EnumCacheSerializer(Class<E> clazz) {
- this.clazz = clazz;
+ this.converter = Enums.stringConverter(clazz);
}
@Override
- public byte[] serialize(E object) throws IOException {
- return object.name().getBytes(UTF_8);
+ public byte[] serialize(E object) {
+ return converter.reverse().convert(checkNotNull(object)).getBytes(UTF_8);
}
@Override
- public E deserialize(byte[] in) throws IOException {
- String name = new String(in, UTF_8);
- return Enums.getIfPresent(clazz, name)
- .toJavaUtil()
- .orElseThrow(() -> new IOException("Invalid " + clazz.getName() + " value: " + name));
+ public E deserialize(byte[] in) {
+ return converter.convert(new String(checkNotNull(in), UTF_8));
}
}
diff --git a/java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java b/java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java
new file mode 100644
index 0000000..a07c004
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/IntKeyCacheSerializer.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gwtorm.client.IntKey;
+import java.util.function.Function;
+
+public class IntKeyCacheSerializer<K extends IntKey<?>> implements CacheSerializer<K> {
+ private final Function<Integer, K> factory;
+
+ public IntKeyCacheSerializer(Function<Integer, K> factory) {
+ this.factory = checkNotNull(factory);
+ }
+
+ @Override
+ public byte[] serialize(K object) {
+ return IntegerCacheSerializer.INSTANCE.serialize(object.get());
+ }
+
+ @Override
+ public K deserialize(byte[] in) {
+ return factory.apply(IntegerCacheSerializer.INSTANCE.deserialize(in));
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/IntegerCacheSerializer.java b/java/com/google/gerrit/server/cache/IntegerCacheSerializer.java
new file mode 100644
index 0000000..5eddb71
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/IntegerCacheSerializer.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.TextFormat;
+import java.io.IOException;
+import java.util.Arrays;
+
+public enum IntegerCacheSerializer implements CacheSerializer<Integer> {
+ INSTANCE;
+
+ // Same as com.google.protobuf.WireFormat#MAX_VARINT_SIZE. Note that negative values take up more
+ // than MAX_VARINT32_SIZE space.
+ private static final int MAX_VARINT_SIZE = 10;
+
+ @Override
+ public byte[] serialize(Integer object) {
+ byte[] buf = new byte[MAX_VARINT_SIZE];
+ CodedOutputStream cout = CodedOutputStream.newInstance(buf);
+ try {
+ cout.writeInt32NoTag(checkNotNull(object));
+ cout.flush();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to serialize int");
+ }
+ int n = cout.getTotalBytesWritten();
+ return n == buf.length ? buf : Arrays.copyOfRange(buf, 0, n);
+ }
+
+ @Override
+ public Integer deserialize(byte[] in) {
+ CodedInputStream cin = CodedInputStream.newInstance(checkNotNull(in));
+ int ret;
+ try {
+ ret = cin.readRawVarint32();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to deserialize int");
+ }
+ int n = cin.getTotalBytesRead();
+ if (n != in.length) {
+ throw new IllegalArgumentException(
+ "Extra bytes in int representation: "
+ + TextFormat.escapeBytes(Arrays.copyOfRange(in, n, in.length)));
+ }
+ return ret;
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/JavaCacheSerializer.java b/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
index 750c5df..55358bc 100644
--- a/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.cache;
+import com.google.gerrit.common.Nullable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -23,29 +24,33 @@
/**
* Serializer that uses default Java serialization.
*
+ * <p>Unlike most {@link CacheSerializer} implementations, serializing null is supported.
+ *
* @param <T> type to serialize. Must implement {@code Serializable}, but due to implementation
* details this is only checked at runtime.
*/
public class JavaCacheSerializer<T> implements CacheSerializer<T> {
@Override
- public byte[] serialize(T object) throws IOException {
+ public byte[] serialize(@Nullable T object) {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout)) {
oout.writeObject(object);
oout.flush();
return bout.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to serialize object", e);
}
}
@SuppressWarnings("unchecked")
@Override
- public T deserialize(byte[] in) throws IOException {
+ public T deserialize(byte[] in) {
Object object;
try (ByteArrayInputStream bin = new ByteArrayInputStream(in);
ObjectInputStream oin = new ObjectInputStream(bin)) {
object = oin.readObject();
- } catch (ClassNotFoundException e) {
- throw new IOException("Failed to deserialize object of type", e);
+ } catch (ClassNotFoundException | IOException e) {
+ throw new IllegalArgumentException("Failed to deserialize object", e);
}
return (T) object;
}
diff --git a/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
index b1a65fe..44e2bb2 100644
--- a/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
@@ -52,11 +52,7 @@
@Override
public void funnel(K from, PrimitiveSink into) {
- try {
- Funnels.byteArrayFunnel().funnel(serializer.serialize(from), into);
- } catch (IOException e) {
- throw new RuntimeException("Cannot hash", e);
- }
+ Funnels.byteArrayFunnel().funnel(serializer.serialize(from), into);
}
};
}
diff --git a/java/com/google/gerrit/server/cache/testing/BUILD b/java/com/google/gerrit/server/cache/testing/BUILD
new file mode 100644
index 0000000..c1293f9
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/testing/BUILD
@@ -0,0 +1,13 @@
+package(default_testonly = 1)
+
+java_library(
+ name = "testing",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//lib:guava",
+ "//lib:protobuf",
+ "//lib:truth",
+ "//lib/commons:lang3",
+ ],
+)
diff --git a/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java b/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java
new file mode 100644
index 0000000..5d41490
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/testing/CacheSerializerTestUtil.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 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.cache.testing;
+
+import com.google.protobuf.ByteString;
+
+/** Static utilities for testing cache serializers. */
+public class CacheSerializerTestUtil {
+ public static ByteString bytes(int... ints) {
+ byte[] bytes = new byte[ints.length];
+ for (int i = 0; i < ints.length; i++) {
+ bytes[i] = (byte) ints[i];
+ }
+ return ByteString.copyFrom(bytes);
+ }
+
+ private CacheSerializerTestUtil() {}
+}
diff --git a/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java b/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java
new file mode 100644
index 0000000..78900cb
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/testing/SerializedClassSubject.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2018 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.cache.testing;
+
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.Map;
+import org.apache.commons.lang3.reflect.FieldUtils;
+
+/**
+ * Subject about classes that are serialized into persistent caches.
+ *
+ * <p>Hand-written {@link com.google.gerrit.server.cache.CacheSerializer CacheSerializer}
+ * implementations depend on the exact representation of the data stored in a class, so it is
+ * important to verify any assumptions about the structure of the serialized classes. This class
+ * contains assertions about serialized classes, and should be used for every class that has a
+ * custom serializer implementation.
+ *
+ * <p>Changing fields of a serialized class (or abstract methods, in the case of {@code @AutoValue}
+ * classes) will likely require changes to the serializer implementation, and may require bumping
+ * the {@link com.google.gerrit.server.cache.PersistentCacheBinding#version(int) version} in the
+ * cache binding, in case the representation has changed in such a way that old serialized data
+ * becomes unreadable.
+ *
+ * <p>Changes to a serialized class such as adding or removing fields generally requires a change to
+ * the hand-written serializer. Usually, serializer implementations should be written in such a way
+ * that new fields are considered optional, and won't require bumping the version.
+ */
+public class SerializedClassSubject extends Subject<SerializedClassSubject, Class<?>> {
+ public static SerializedClassSubject assertThatSerializedClass(Class<?> actual) {
+ // This formulation fails in Eclipse 4.7.3a with "The type
+ // SerializedClassSubject does not define SerializedClassSubject() that is
+ // applicable here", due to
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=534694 or a similar bug:
+ // return assertAbout(SerializedClassSubject::new).that(actual);
+ Subject.Factory<SerializedClassSubject, Class<?>> factory =
+ (m, a) -> new SerializedClassSubject(m, a);
+ return assertAbout(factory).that(actual);
+ }
+
+ private SerializedClassSubject(FailureMetadata metadata, Class<?> actual) {
+ super(metadata, actual);
+ }
+
+ public void isConcrete() {
+ isNotNull();
+ assertWithMessage("expected class %s to be concrete", actual().getName())
+ .that(!Modifier.isAbstract(actual().getModifiers()))
+ .isTrue();
+ }
+
+ public void hasFields(Map<String, Type> expectedFields) {
+ isConcrete();
+ assertThat(
+ FieldUtils.getAllFieldsList(actual())
+ .stream()
+ .filter(f -> !Modifier.isStatic(f.getModifiers()))
+ .collect(toImmutableMap(Field::getName, Field::getGenericType)))
+ .containsExactlyEntriesIn(expectedFields);
+ }
+}
diff --git a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 6449662..a4eb90f 100644
--- a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -42,6 +42,7 @@
import com.google.inject.Module;
import com.google.inject.name.Named;
import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
@@ -176,7 +177,7 @@
@VisibleForTesting
static class Serializer implements CacheSerializer<Key> {
@Override
- public byte[] serialize(Key object) throws IOException {
+ public byte[] serialize(Key object) {
byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
ChangeKindKeyProto.Builder b = ChangeKindKeyProto.newBuilder();
object.getPrior().copyRawTo(buf, 0);
@@ -188,12 +189,16 @@
}
@Override
- public Key deserialize(byte[] in) throws IOException {
- ChangeKindKeyProto proto = ChangeKindKeyProto.parseFrom(in);
- return new Key(
- ObjectId.fromRaw(proto.getPrior().toByteArray()),
- ObjectId.fromRaw(proto.getNext().toByteArray()),
- proto.getStrategyName());
+ public Key deserialize(byte[] in) {
+ try {
+ ChangeKindKeyProto proto = ChangeKindKeyProto.parseFrom(in);
+ return new Key(
+ ObjectId.fromRaw(proto.getPrior().toByteArray()),
+ ObjectId.fromRaw(proto.getNext().toByteArray()),
+ proto.getStrategyName());
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalArgumentException("Failed to deserialize object", e);
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index ea91e0f..a192228 100644
--- a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -16,20 +16,20 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
-import static org.eclipse.jgit.lib.ObjectIdSerializer.readWithoutMarker;
-import static org.eclipse.jgit.lib.ObjectIdSerializer.writeWithoutMarker;
+import com.google.common.base.Converter;
+import com.google.common.base.Enums;
import com.google.common.base.MoreObjects;
import com.google.common.cache.Cache;
import com.google.common.cache.Weigher;
-import com.google.common.collect.ImmutableBiMap;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.server.cache.BooleanCacheSerializer;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.submit.SubmitDryRun;
@@ -37,14 +37,13 @@
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
+import com.google.protobuf.ByteString;
import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -58,30 +57,16 @@
private static final String CACHE_NAME = "mergeability";
- public static final ImmutableBiMap<SubmitType, Character> SUBMIT_TYPES =
- new ImmutableBiMap.Builder<SubmitType, Character>()
- .put(SubmitType.INHERIT, 'I')
- .put(SubmitType.FAST_FORWARD_ONLY, 'F')
- .put(SubmitType.MERGE_IF_NECESSARY, 'M')
- .put(SubmitType.REBASE_ALWAYS, 'P')
- .put(SubmitType.REBASE_IF_NECESSARY, 'R')
- .put(SubmitType.MERGE_ALWAYS, 'A')
- .put(SubmitType.CHERRY_PICK, 'C')
- .build();
-
- static {
- checkState(
- SUBMIT_TYPES.size() == SubmitType.values().length,
- "SubmitType <-> char BiMap needs updating");
- }
-
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
persist(CACHE_NAME, EntryKey.class, Boolean.class)
.maximumWeight(1 << 20)
- .weigher(MergeabilityWeigher.class);
+ .weigher(MergeabilityWeigher.class)
+ .version(1)
+ .keySerializer(EntryKey.Serializer.INSTANCE)
+ .valueSerializer(BooleanCacheSerializer.INSTANCE);
bind(MergeabilityCache.class).to(MergeabilityCacheImpl.class);
}
};
@@ -91,9 +76,7 @@
return ref != null && ref.getObjectId() != null ? ref.getObjectId() : ObjectId.zeroId();
}
- public static class EntryKey implements Serializable {
- private static final long serialVersionUID = 1L;
-
+ public static class EntryKey {
private ObjectId commit;
private ObjectId into;
private SubmitType submitType;
@@ -154,26 +137,44 @@
.toString();
}
- private void writeObject(ObjectOutputStream out) throws IOException {
- writeWithoutMarker(out, commit);
- writeWithoutMarker(out, into);
- Character c = SUBMIT_TYPES.get(submitType);
- if (c == null) {
- throw new IOException("Invalid submit type: " + submitType);
- }
- out.writeChar(c);
- writeString(out, mergeStrategy);
- }
+ static enum Serializer implements CacheSerializer<EntryKey> {
+ INSTANCE;
- private void readObject(ObjectInputStream in) throws IOException {
- commit = readWithoutMarker(in);
- into = readWithoutMarker(in);
- char t = in.readChar();
- submitType = SUBMIT_TYPES.inverse().get(t);
- if (submitType == null) {
- throw new IOException("Invalid submit type code: " + t);
+ private static final Converter<String, SubmitType> SUBMIT_TYPE_CONVERTER =
+ Enums.stringConverter(SubmitType.class);
+
+ @Override
+ public byte[] serialize(EntryKey object) {
+ byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ MergeabilityKeyProto.Builder b = MergeabilityKeyProto.newBuilder();
+ object.getCommit().copyRawTo(buf, 0);
+ b.setCommit(ByteString.copyFrom(buf));
+ object.getInto().copyRawTo(buf, 0);
+ b.setInto(ByteString.copyFrom(buf));
+ b.setSubmitType(SUBMIT_TYPE_CONVERTER.reverse().convert(object.getSubmitType()));
+ b.setMergeStrategy(object.getMergeStrategy());
+ return ProtoCacheSerializers.toByteArray(b.build());
}
- mergeStrategy = readString(in);
+
+ @Override
+ public EntryKey deserialize(byte[] in) {
+ MergeabilityKeyProto proto;
+ try {
+ proto = MergeabilityKeyProto.parseFrom(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to deserialize mergeability cache key");
+ }
+ byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ proto.getCommit().copyTo(buf, 0);
+ ObjectId commit = ObjectId.fromRaw(buf);
+ proto.getInto().copyTo(buf, 0);
+ ObjectId into = ObjectId.fromRaw(buf);
+ return new EntryKey(
+ commit,
+ into,
+ SUBMIT_TYPE_CONVERTER.convert(proto.getSubmitType()),
+ proto.getMergeStrategy());
+ }
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 68b1ff9..5db347e 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -22,6 +22,9 @@
import static com.google.gerrit.index.FieldDef.prefix;
import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
+import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -64,7 +67,6 @@
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
import com.google.gson.Gson;
-import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
import com.google.protobuf.CodedOutputStream;
@@ -468,15 +470,10 @@
exact(ChangeQueryBuilder.FIELD_EXACTCOMMITTER)
.buildRepeatable(ChangeField::getCommitterNameAndEmail);
- public static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
-
/** Serialized change object, used for pre-populating results. */
public static final FieldDef<ChangeData, byte[]> CHANGE =
storedOnly("_change").build(changeGetter(CHANGE_CODEC::encodeToByteArray));
- public static final ProtobufCodec<PatchSetApproval> APPROVAL_CODEC =
- CodecFactory.encoder(PatchSetApproval.class);
-
/** Serialized approvals for the current patch set, used for pre-populating results. */
public static final FieldDef<ChangeData, Iterable<byte[]>> APPROVAL =
storedOnly("_approval")
@@ -596,9 +593,6 @@
cd ->
cd.patchSets().stream().flatMap(ps -> ps.getGroups().stream()).collect(toSet()));
- public static final ProtobufCodec<PatchSet> PATCH_SET_CODEC =
- CodecFactory.encoder(PatchSet.class);
-
/** Serialized patch set object, used for pre-populating results. */
public static final FieldDef<ChangeData, Iterable<byte[]>> PATCH_SET =
storedOnly("_patch_set").buildRepeatable(cd -> toProtos(PATCH_SET_CODEC, cd.patchSets()));
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index f5fefeb..1bbecc8 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -564,12 +564,7 @@
/** @return all change messages, in chronological order, oldest first. */
public ImmutableList<ChangeMessage> getChangeMessages() {
- return state.allChangeMessages();
- }
-
- /** @return change messages by patch set, in chronological order, oldest first. */
- public ImmutableListMultimap<PatchSet.Id, ChangeMessage> getChangeMessagesByPatchSet() {
- return state.changeMessagesByPatchSet();
+ return state.changeMessages();
}
/** @return inline comments on each revision. */
@@ -670,28 +665,6 @@
return state.readOnlyUntil();
}
- public boolean isPrivate() {
- if (state.isPrivate() == null) {
- return false;
- }
- return state.isPrivate();
- }
-
- public boolean isWorkInProgress() {
- if (state.isWorkInProgress() == null) {
- return false;
- }
- return state.isWorkInProgress();
- }
-
- public Change.Id getRevertOf() {
- return state.revertOf();
- }
-
- public boolean hasReviewStarted() {
- return state.hasReviewStarted();
- }
-
@Override
protected void onLoad(LoadHandle handle)
throws NoSuchChangeException, IOException, ConfigInvalidException {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index 676dbb8..5658569 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -128,10 +128,7 @@
+ P
+ list(state.submitRecords(), P + list(2, str(4) + P + K) + P)
+ P
- + list(state.allChangeMessages(), changeMessage())
- // Just key overhead for map, already counted messages in previous.
- + P
- + map(state.changeMessagesByPatchSet().asMap(), patchSetId())
+ + list(state.changeMessages(), changeMessage())
+ P
+ map(state.publishedComments().asMap(), comment())
+ T // readOnlyUntil
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index d6472bc..2eb30ff 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.notedb;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
@@ -44,7 +45,6 @@
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
-import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MultimapBuilder;
@@ -145,7 +145,6 @@
private final Map<ApprovalKey, PatchSetApproval> approvals;
private final List<PatchSetApproval> bufferedApprovals;
private final List<ChangeMessage> allChangeMessages;
- private final ListMultimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet;
// Non-final private members filled in during the parsing process.
private String branch;
@@ -193,7 +192,6 @@
reviewerUpdates = new ArrayList<>();
submitRecords = Lists.newArrayListWithExpectedSize(1);
allChangeMessages = new ArrayList<>();
- changeMessagesByPatchSet = LinkedListMultimap.create();
comments = MultimapBuilder.hashKeys().arrayListValues().build();
patchSets = new HashMap<>();
deletedPatchSets = new HashSet<>();
@@ -253,7 +251,7 @@
assignee != null ? assignee.orElse(null) : null,
status,
Sets.newLinkedHashSet(Lists.reverse(pastAssignees)),
- hashtags,
+ firstNonNull(hashtags, ImmutableSet.of()),
patchSets,
buildApprovals(),
ReviewerSet.fromTable(Tables.transpose(reviewers)),
@@ -264,12 +262,11 @@
buildReviewerUpdates(),
submitRecords,
buildAllMessages(),
- buildMessagesByPatchSet(),
comments,
readOnlyUntil,
- isPrivate,
- workInProgress,
- hasReviewStarted,
+ firstNonNull(isPrivate, false),
+ firstNonNull(workInProgress, false),
+ firstNonNull(hasReviewStarted, true),
revertOf);
}
@@ -318,13 +315,6 @@
return Lists.reverse(allChangeMessages);
}
- private ListMultimap<PatchSet.Id, ChangeMessage> buildMessagesByPatchSet() {
- for (Collection<ChangeMessage> v : changeMessagesByPatchSet.asMap().values()) {
- Collections.reverse((List<ChangeMessage>) v);
- }
- return changeMessagesByPatchSet;
- }
-
private void parse(ChangeNotesCommit commit) throws ConfigInvalidException {
Timestamp ts = new Timestamp(commit.getCommitterIdent().getWhen().getTime());
@@ -751,7 +741,6 @@
changeMessage.setMessage(changeMsgString);
changeMessage.setTag(tag);
changeMessage.setRealAuthor(realAccountId);
- changeMessagesByPatchSet.put(psId, changeMessage);
allChangeMessages.add(changeMessage);
}
@@ -1088,8 +1077,6 @@
// (or otherwise missing) patch sets. This is safer than trying to prevent
// insertion, as it will also filter out items racily added after the patch
// set was deleted.
- changeMessagesByPatchSet.keys().retainAll(patchSets.keySet());
-
int pruned =
pruneEntitiesForMissingPatchSets(allChangeMessages, ChangeMessage::getPatchSetId, missing);
pruned +=
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 1dd944d..78734f9 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -57,33 +57,15 @@
@AutoValue
public abstract class ChangeNotesState {
static ChangeNotesState empty(Change change) {
- return new AutoValue_ChangeNotesState(
- null,
- change.getId(),
- null,
- ImmutableSet.of(),
- ImmutableSet.of(),
- ImmutableList.of(),
- ImmutableList.of(),
- ReviewerSet.empty(),
- ReviewerByEmailSet.empty(),
- ReviewerSet.empty(),
- ReviewerByEmailSet.empty(),
- ImmutableList.of(),
- ImmutableList.of(),
- ImmutableList.of(),
- ImmutableList.of(),
- ImmutableListMultimap.of(),
- ImmutableListMultimap.of(),
- null,
- null,
- null,
- true,
- null);
+ return Builder.empty(change.getId()).build();
+ }
+
+ static Builder builder() {
+ return new AutoValue_ChangeNotesState.Builder();
}
static ChangeNotesState create(
- @Nullable ObjectId metaId,
+ ObjectId metaId,
Change.Id changeId,
Change.Key changeKey,
Timestamp createdOn,
@@ -97,8 +79,8 @@
@Nullable String submissionId,
@Nullable Account.Id assignee,
@Nullable Change.Status status,
- @Nullable Set<Account.Id> pastAssignees,
- @Nullable Set<String> hashtags,
+ Set<Account.Id> pastAssignees,
+ Set<String> hashtags,
Map<PatchSet.Id, PatchSet> patchSets,
ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
ReviewerSet reviewers,
@@ -108,56 +90,55 @@
List<Account.Id> allPastReviewers,
List<ReviewerStatusUpdate> reviewerUpdates,
List<SubmitRecord> submitRecords,
- List<ChangeMessage> allChangeMessages,
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet,
+ List<ChangeMessage> changeMessages,
ListMultimap<RevId, Comment> publishedComments,
@Nullable Timestamp readOnlyUntil,
- @Nullable Boolean isPrivate,
- @Nullable Boolean workInProgress,
+ boolean isPrivate,
+ boolean workInProgress,
boolean hasReviewStarted,
@Nullable Change.Id revertOf) {
- if (hashtags == null) {
- hashtags = ImmutableSet.of();
- }
- return new AutoValue_ChangeNotesState(
+ checkNotNull(
metaId,
- changeId,
- new AutoValue_ChangeNotesState_ChangeColumns(
- changeKey,
- createdOn,
- lastUpdatedOn,
- owner,
- branch,
- currentPatchSetId,
- subject,
- topic,
- originalSubject,
- submissionId,
- assignee,
- status,
- isPrivate,
- workInProgress,
- hasReviewStarted,
- revertOf),
- ImmutableSet.copyOf(pastAssignees),
- ImmutableSet.copyOf(hashtags),
- ImmutableList.copyOf(patchSets.entrySet()),
- ImmutableList.copyOf(approvals.entries()),
- reviewers,
- reviewersByEmail,
- pendingReviewers,
- pendingReviewersByEmail,
- ImmutableList.copyOf(allPastReviewers),
- ImmutableList.copyOf(reviewerUpdates),
- ImmutableList.copyOf(submitRecords),
- ImmutableList.copyOf(allChangeMessages),
- ImmutableListMultimap.copyOf(changeMessagesByPatchSet),
- ImmutableListMultimap.copyOf(publishedComments),
- readOnlyUntil,
- isPrivate,
- workInProgress,
- hasReviewStarted,
- revertOf);
+ "metaId is required when passing arguments to create(...). To create an empty %s without"
+ + " NoteDb data, use empty(...) instead",
+ ChangeNotesState.class.getSimpleName());
+ return builder()
+ .metaId(metaId)
+ .changeId(changeId)
+ .columns(
+ new AutoValue_ChangeNotesState_ChangeColumns.Builder()
+ .changeKey(changeKey)
+ .createdOn(createdOn)
+ .lastUpdatedOn(lastUpdatedOn)
+ .owner(owner)
+ .branch(branch)
+ .currentPatchSetId(currentPatchSetId)
+ .subject(subject)
+ .topic(topic)
+ .originalSubject(originalSubject)
+ .submissionId(submissionId)
+ .assignee(assignee)
+ .status(status)
+ .isPrivate(isPrivate)
+ .isWorkInProgress(workInProgress)
+ .hasReviewStarted(hasReviewStarted)
+ .revertOf(revertOf)
+ .build())
+ .pastAssignees(pastAssignees)
+ .hashtags(hashtags)
+ .patchSets(patchSets.entrySet())
+ .approvals(approvals.entries())
+ .reviewers(reviewers)
+ .reviewersByEmail(reviewersByEmail)
+ .pendingReviewers(pendingReviewers)
+ .pendingReviewersByEmail(pendingReviewersByEmail)
+ .allPastReviewers(allPastReviewers)
+ .reviewerUpdates(reviewerUpdates)
+ .submitRecords(submitRecords)
+ .changeMessages(changeMessages)
+ .publishedComments(publishedComments)
+ .readOnlyUntil(readOnlyUntil)
+ .build();
}
/**
@@ -201,17 +182,51 @@
@Nullable
abstract Change.Status status();
- @Nullable
- abstract Boolean isPrivate();
+ abstract boolean isPrivate();
- @Nullable
- abstract Boolean isWorkInProgress();
+ abstract boolean isWorkInProgress();
- @Nullable
- abstract Boolean hasReviewStarted();
+ abstract boolean hasReviewStarted();
@Nullable
abstract Change.Id revertOf();
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder changeKey(Change.Key changeKey);
+
+ abstract Builder createdOn(Timestamp createdOn);
+
+ abstract Builder lastUpdatedOn(Timestamp lastUpdatedOn);
+
+ abstract Builder owner(Account.Id owner);
+
+ abstract Builder branch(String branch);
+
+ abstract Builder currentPatchSetId(@Nullable PatchSet.Id currentPatchSetId);
+
+ abstract Builder subject(String subject);
+
+ abstract Builder topic(@Nullable String topic);
+
+ abstract Builder originalSubject(@Nullable String originalSubject);
+
+ abstract Builder submissionId(@Nullable String submissionId);
+
+ abstract Builder assignee(@Nullable Account.Id assignee);
+
+ abstract Builder status(@Nullable Change.Status status);
+
+ abstract Builder isPrivate(boolean isPrivate);
+
+ abstract Builder isWorkInProgress(boolean isWorkInProgress);
+
+ abstract Builder hasReviewStarted(boolean hasReviewStarted);
+
+ abstract Builder revertOf(@Nullable Change.Id revertOf);
+
+ abstract ChangeColumns build();
+ }
}
// Only null if NoteDb is disabled.
@@ -247,27 +262,13 @@
abstract ImmutableList<SubmitRecord> submitRecords();
- abstract ImmutableList<ChangeMessage> allChangeMessages();
-
- abstract ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet();
+ abstract ImmutableList<ChangeMessage> changeMessages();
abstract ImmutableListMultimap<RevId, Comment> publishedComments();
@Nullable
abstract Timestamp readOnlyUntil();
- @Nullable
- abstract Boolean isPrivate();
-
- @Nullable
- abstract Boolean isWorkInProgress();
-
- @Nullable
- abstract Boolean hasReviewStarted();
-
- @Nullable
- abstract Change.Id revertOf();
-
Change newChange(Project.NameKey project) {
ChangeColumns c = checkNotNull(columns(), "columns are required");
Change change =
@@ -325,9 +326,9 @@
change.setLastUpdatedOn(c.lastUpdatedOn());
change.setSubmissionId(c.submissionId());
change.setAssignee(c.assignee());
- change.setPrivate(c.isPrivate() == null ? false : c.isPrivate());
- change.setWorkInProgress(c.isWorkInProgress() == null ? false : c.isWorkInProgress());
- change.setReviewStarted(c.hasReviewStarted() == null ? false : c.hasReviewStarted());
+ change.setPrivate(c.isPrivate());
+ change.setWorkInProgress(c.isWorkInProgress());
+ change.setReviewStarted(c.hasReviewStarted());
change.setRevertOf(c.revertOf());
if (!patchSets().isEmpty()) {
@@ -338,4 +339,61 @@
change.clearCurrentPatchSet();
}
}
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ static Builder empty(Change.Id changeId) {
+ return new AutoValue_ChangeNotesState.Builder()
+ .changeId(changeId)
+ .pastAssignees(ImmutableSet.of())
+ .hashtags(ImmutableSet.of())
+ .patchSets(ImmutableList.of())
+ .approvals(ImmutableList.of())
+ .reviewers(ReviewerSet.empty())
+ .reviewersByEmail(ReviewerByEmailSet.empty())
+ .pendingReviewers(ReviewerSet.empty())
+ .pendingReviewersByEmail(ReviewerByEmailSet.empty())
+ .allPastReviewers(ImmutableList.of())
+ .reviewerUpdates(ImmutableList.of())
+ .submitRecords(ImmutableList.of())
+ .changeMessages(ImmutableList.of())
+ .publishedComments(ImmutableListMultimap.of());
+ }
+
+ abstract Builder metaId(ObjectId metaId);
+
+ abstract Builder changeId(Change.Id changeId);
+
+ abstract Builder columns(ChangeColumns columns);
+
+ abstract Builder pastAssignees(Set<Account.Id> pastAssignees);
+
+ abstract Builder hashtags(Set<String> hashtags);
+
+ abstract Builder patchSets(Iterable<Map.Entry<PatchSet.Id, PatchSet>> patchSets);
+
+ abstract Builder approvals(Iterable<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals);
+
+ abstract Builder reviewers(ReviewerSet reviewers);
+
+ abstract Builder reviewersByEmail(ReviewerByEmailSet reviewersByEmail);
+
+ abstract Builder pendingReviewers(ReviewerSet pendingReviewers);
+
+ abstract Builder pendingReviewersByEmail(ReviewerByEmailSet pendingReviewersByEmail);
+
+ abstract Builder allPastReviewers(List<Account.Id> allPastReviewers);
+
+ abstract Builder reviewerUpdates(List<ReviewerStatusUpdate> reviewerUpdates);
+
+ abstract Builder submitRecords(List<SubmitRecord> submitRecords);
+
+ abstract Builder changeMessages(List<ChangeMessage> changeMessages);
+
+ abstract Builder publishedComments(ListMultimap<RevId, Comment> publishedComments);
+
+ abstract Builder readOnlyUntil(@Nullable Timestamp readOnlyUntil);
+
+ abstract ChangeNotesState build();
+ }
}
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index b13d921..3a17965 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -29,6 +29,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.query.change.ChangeData;
@@ -47,11 +48,16 @@
static class Factory {
private final ChangeData.Factory changeDataFactory;
private final ChangeNotes.Factory notesFactory;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
@Inject
- Factory(ChangeData.Factory changeDataFactory, ChangeNotes.Factory notesFactory) {
+ Factory(
+ ChangeData.Factory changeDataFactory,
+ ChangeNotes.Factory notesFactory,
+ IdentifiedUser.GenericFactory identifiedUserFactory) {
this.changeDataFactory = changeDataFactory;
this.notesFactory = notesFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
}
ChangeControl create(
@@ -61,17 +67,22 @@
}
ChangeControl create(RefControl refControl, ChangeNotes notes) {
- return new ChangeControl(changeDataFactory, refControl, notes);
+ return new ChangeControl(changeDataFactory, identifiedUserFactory, refControl, notes);
}
}
private final ChangeData.Factory changeDataFactory;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final RefControl refControl;
private final ChangeNotes notes;
private ChangeControl(
- ChangeData.Factory changeDataFactory, RefControl refControl, ChangeNotes notes) {
+ ChangeData.Factory changeDataFactory,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ RefControl refControl,
+ ChangeNotes notes) {
this.changeDataFactory = changeDataFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
this.refControl = refControl;
this.notes = notes;
}
@@ -84,7 +95,8 @@
if (getUser().equals(who)) {
return this;
}
- return new ChangeControl(changeDataFactory, refControl.forUser(who), notes);
+ return new ChangeControl(
+ changeDataFactory, identifiedUserFactory, refControl.forUser(who), notes);
}
private CurrentUser getUser() {
@@ -261,6 +273,11 @@
}
@Override
+ public ForChange absentUser(Account.Id id) {
+ return user(identifiedUserFactory.create(id));
+ }
+
+ @Override
public String resourcePath() {
if (resourcePath == null) {
resourcePath =
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
index 02eed30..490b45e 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -79,8 +79,8 @@
}
@Override
- public WithUser absentUser(Account.Id user) {
- IdentifiedUser identifiedUser = identifiedUserFactory.create(checkNotNull(user, "user"));
+ public WithUser absentUser(Account.Id id) {
+ IdentifiedUser identifiedUser = identifiedUserFactory.create(checkNotNull(id, "user"));
return new WithUserImpl(identifiedUser);
}
diff --git a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
index 35b6e0d..431bfd9 100644
--- a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.permissions;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -129,6 +130,11 @@
}
@Override
+ public ForProject absentUser(Account.Id id) {
+ return this;
+ }
+
+ @Override
public String resourcePath() {
throw new UnsupportedOperationException(
"FailedPermissionBackend is not scoped to a resource");
@@ -182,6 +188,11 @@
}
@Override
+ public ForRef absentUser(Account.Id id) {
+ return this;
+ }
+
+ @Override
public String resourcePath() {
throw new UnsupportedOperationException(
"FailedPermissionBackend is not scoped to a resource");
@@ -234,6 +245,11 @@
}
@Override
+ public ForChange absentUser(Account.Id id) {
+ return this;
+ }
+
+ @Override
public String resourcePath() {
throw new UnsupportedOperationException(
"FailedPermissionBackend is not scoped to a resource");
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 4cbd77e..8cdb61d 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -112,7 +112,7 @@
*
* <p>Usage should be very limited as this can expose a group-oracle.
*/
- public abstract WithUser absentUser(Account.Id user);
+ public abstract WithUser absentUser(Account.Id id);
/**
* Check whether this {@code PermissionBackend} respects the same global capabilities as the
@@ -305,6 +305,9 @@
/** Returns a new instance rescoped to same project, but different {@code user}. */
public abstract ForProject user(CurrentUser user);
+ /** @see PermissionBackend#absentUser(Account.Id) */
+ public abstract ForProject absentUser(Account.Id id);
+
/** Returns an instance scoped for {@code ref} in this project. */
public abstract ForRef ref(String ref);
@@ -413,6 +416,9 @@
/** Returns a new instance rescoped to same reference, but different {@code user}. */
public abstract ForRef user(CurrentUser user);
+ /** @see PermissionBackend#absentUser(Account.Id) */
+ public abstract ForRef absentUser(Account.Id id);
+
/** Returns an instance scoped to change. */
public abstract ForChange change(ChangeData cd);
@@ -471,6 +477,9 @@
/** Returns a new instance rescoped to same change, but different {@code user}. */
public abstract ForChange user(CurrentUser user);
+ /** @see PermissionBackend#absentUser(Account.Id) */
+ public abstract ForChange absentUser(Account.Id id);
+
/** Verify scoped user can {@code perm}, throwing if denied. */
public abstract void check(ChangePermissionOrLabel perm)
throws AuthException, PermissionBackendException;
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index dbd60ea..2d2a64d 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -27,6 +28,7 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
@@ -67,6 +69,7 @@
private final ChangeControl.Factory changeControlFactory;
private final PermissionCollection.Factory permissionFilter;
private final DefaultRefFilter.Factory refFilterFactory;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
private List<SectionMatcher> allSections;
private Map<String, RefControl> refControls;
@@ -80,6 +83,7 @@
ChangeControl.Factory changeControlFactory,
PermissionBackend permissionBackend,
DefaultRefFilter.Factory refFilterFactory,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
@Assisted CurrentUser who,
@Assisted ProjectState ps) {
this.changeControlFactory = changeControlFactory;
@@ -88,6 +92,7 @@
this.permissionFilter = permissionFilter;
this.permissionBackend = permissionBackend;
this.refFilterFactory = refFilterFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
user = who;
state = ps;
}
@@ -101,6 +106,7 @@
changeControlFactory,
permissionBackend,
refFilterFactory,
+ identifiedUserFactory,
who,
state);
// Not per-user, and reusing saves lookup time.
@@ -132,7 +138,7 @@
RefControl ctl = refControls.get(refName);
if (ctl == null) {
PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
- ctl = new RefControl(this, refName, relevant);
+ ctl = new RefControl(identifiedUserFactory, this, refName, relevant);
refControls.put(refName, ctl);
}
return ctl;
@@ -327,6 +333,11 @@
}
@Override
+ public ForProject absentUser(Account.Id id) {
+ return user(identifiedUserFactory.create(id));
+ }
+
+ @Override
public String resourcePath() {
if (resourcePath == null) {
resourcePath = "/projects/" + getProjectState().getName();
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 28781ea..cd1f84a 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -21,10 +21,12 @@
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
@@ -39,6 +41,7 @@
/** Manages access control for Git references (aka branches, tags). */
class RefControl {
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ProjectControl projectControl;
private final String refName;
@@ -52,7 +55,12 @@
private Boolean canForgeCommitter;
private Boolean isVisible;
- RefControl(ProjectControl projectControl, String ref, PermissionCollection relevant) {
+ RefControl(
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ ProjectControl projectControl,
+ String ref,
+ PermissionCollection relevant) {
+ this.identifiedUserFactory = identifiedUserFactory;
this.projectControl = projectControl;
this.refName = ref;
this.relevant = relevant;
@@ -71,7 +79,7 @@
if (relevant.isUserSpecific()) {
return newCtl.controlForRef(refName);
}
- return new RefControl(newCtl, refName, relevant);
+ return new RefControl(identifiedUserFactory, newCtl, refName, relevant);
}
/** Is this user a ref owner? */
@@ -404,6 +412,11 @@
}
@Override
+ public ForRef absentUser(Account.Id id) {
+ return user(identifiedUserFactory.create(id));
+ }
+
+ @Override
public String resourcePath() {
if (resourcePath == null) {
resourcePath =
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewers.java b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
index 46955e8..65c7db7 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
@@ -98,7 +98,6 @@
private final AccountLoader.Factory accountLoaderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeData.Factory changeDataFactory;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final Config cfg;
private final ReviewerJson json;
private final NotesMigration migration;
@@ -118,7 +117,6 @@
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
RetryHelper retryHelper,
- IdentifiedUser.GenericFactory identifiedUserFactory,
@GerritServerConfig Config cfg,
ReviewerJson json,
NotesMigration migration,
@@ -135,7 +133,6 @@
this.accountLoaderFactory = accountLoaderFactory;
this.dbProvider = db;
this.changeDataFactory = changeDataFactory;
- this.identifiedUserFactory = identifiedUserFactory;
this.cfg = cfg;
this.json = json;
this.migration = migration;
@@ -376,18 +373,18 @@
private boolean isValidReviewer(Account member, PermissionBackend.ForRef perm)
throws PermissionBackendException {
- if (member.isActive()) {
- IdentifiedUser user = identifiedUserFactory.create(member.getId());
- // Does not account for draft status as a user might want to let a
- // reviewer see a draft.
- try {
- perm.user(user).check(RefPermission.READ);
- return true;
- } catch (AuthException e) {
- return false;
- }
+ if (!member.isActive()) {
+ return false;
}
- return false;
+
+ // Does not account for draft status as a user might want to let a
+ // reviewer see a draft.
+ try {
+ perm.absentUser(member.getId()).check(RefPermission.READ);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ }
}
private Addition fail(String reviewer, String error) {
@@ -464,8 +461,8 @@
if (migration.readChanges() && state == CC) {
result.ccs = Lists.newArrayListWithCapacity(opResult.addedCCs().size());
for (Account.Id accountId : opResult.addedCCs()) {
- IdentifiedUser u = identifiedUserFactory.create(accountId);
- result.ccs.add(json.format(new ReviewerInfo(accountId.get()), perm.user(u), cd));
+ result.ccs.add(
+ json.format(new ReviewerInfo(accountId.get()), perm.absentUser(accountId), cd));
}
accountLoaderFactory.create(true).fill(result.ccs);
for (Address a : reviewersByEmail) {
@@ -475,11 +472,10 @@
result.reviewers = Lists.newArrayListWithCapacity(opResult.addedReviewers().size());
for (PatchSetApproval psa : opResult.addedReviewers()) {
// New reviewers have value 0, don't bother normalizing.
- IdentifiedUser u = identifiedUserFactory.create(psa.getAccountId());
result.reviewers.add(
json.format(
new ReviewerInfo(psa.getAccountId().get()),
- perm.user(u),
+ perm.absentUser(psa.getAccountId()),
cd,
ImmutableList.of(psa)));
}
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index be63e5d..54ecd18 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -18,7 +18,6 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
@@ -33,12 +32,10 @@
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -119,7 +116,6 @@
private final GitRepositoryManager repoManager;
private final PermissionBackend permissionBackend;
private final ChangeData.Factory changeDataFactory;
- private final ChangeMessagesUtil cmUtil;
private final ChangeNotes.Factory changeNotesFactory;
private final Provider<MergeOp> mergeOpProvider;
private final Provider<MergeSuperSet> mergeSuperSet;
@@ -141,7 +137,6 @@
GitRepositoryManager repoManager,
PermissionBackend permissionBackend,
ChangeData.Factory changeDataFactory,
- ChangeMessagesUtil cmUtil,
ChangeNotes.Factory changeNotesFactory,
Provider<MergeOp> mergeOpProvider,
Provider<MergeSuperSet> mergeSuperSet,
@@ -154,7 +149,6 @@
this.repoManager = repoManager;
this.permissionBackend = permissionBackend;
this.changeDataFactory = changeDataFactory;
- this.cmUtil = cmUtil;
this.changeNotesFactory = changeNotesFactory;
this.mergeOpProvider = mergeOpProvider;
this.mergeSuperSet = mergeSuperSet;
@@ -237,11 +231,8 @@
case MERGED:
return change;
case NEW:
- ChangeMessage msg = getConflictMessage(rsrc);
- if (msg != null) {
- throw new ResourceConflictException(msg.getMessage());
- }
- // $FALL-THROUGH$
+ throw new RestApiException(
+ "change unexpectedly had status " + change.getStatus() + " after submit attempt");
case ABANDONED:
default:
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
@@ -394,18 +385,6 @@
.setEnabled(Boolean.TRUE.equals(enabled));
}
- /**
- * If the merge was attempted and it failed the system usually writes a comment as a ChangeMessage
- * and sets status to NEW. Find the relevant message and return it.
- */
- public ChangeMessage getConflictMessage(RevisionResource rsrc) throws OrmException {
- return FluentIterable.from(
- cmUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(), rsrc.getPatchSet().getId()))
- .filter(cm -> cm.getAuthor() == null)
- .last()
- .orNull();
- }
-
public Collection<ChangeData> unmergeableChanges(ChangeSet cs) throws OrmException, IOException {
Set<ChangeData> mergeabilityMap = new HashSet<>();
for (ChangeData change : cs.changes()) {
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 3864676..1ab1124 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -42,6 +42,7 @@
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/cache/testing",
"//java/com/google/gerrit/server/group/testing",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/server/restapi",
diff --git a/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java b/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java
new file mode 100644
index 0000000..586c065
--- /dev/null
+++ b/javatests/com/google/gerrit/server/auth/oauth/OAuthTokenCacheTest.java
@@ -0,0 +1,74 @@
+package com.google.gerrit.server.auth.oauth;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto;
+import java.lang.reflect.Type;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class OAuthTokenCacheTest {
+ @Test
+ public void oAuthTokenSerializer() throws Exception {
+ OAuthToken token = new OAuthToken("token", "secret", "raw", 12345L, "provider");
+ CacheSerializer<OAuthToken> s = new OAuthTokenCache.Serializer();
+ byte[] serialized = s.serialize(token);
+ assertThat(OAuthTokenProto.parseFrom(serialized))
+ .isEqualTo(
+ OAuthTokenProto.newBuilder()
+ .setToken("token")
+ .setSecret("secret")
+ .setRaw("raw")
+ .setExpiresAt(12345L)
+ .setProviderId("provider")
+ .build());
+ assertThat(s.deserialize(serialized)).isEqualTo(token);
+ }
+
+ @Test
+ public void oAuthTokenSerializerWithNullProvider() throws Exception {
+ OAuthToken tokenWithNull = new OAuthToken("token", "secret", "raw", 12345L, null);
+ CacheSerializer<OAuthToken> s = new OAuthTokenCache.Serializer();
+ OAuthTokenProto expectedProto =
+ OAuthTokenProto.newBuilder()
+ .setToken("token")
+ .setSecret("secret")
+ .setRaw("raw")
+ .setExpiresAt(12345L)
+ .setProviderId("")
+ .build();
+
+ byte[] serializedWithNull = s.serialize(tokenWithNull);
+ assertThat(OAuthTokenProto.parseFrom(serializedWithNull)).isEqualTo(expectedProto);
+ assertThat(s.deserialize(serializedWithNull)).isEqualTo(tokenWithNull);
+
+ OAuthToken tokenWithEmptyString = new OAuthToken("token", "secret", "raw", 12345L, "");
+ assertThat(tokenWithEmptyString).isEqualTo(tokenWithNull);
+ byte[] serializedWithEmptyString = s.serialize(tokenWithEmptyString);
+ assertThat(OAuthTokenProto.parseFrom(serializedWithEmptyString)).isEqualTo(expectedProto);
+ assertThat(s.deserialize(serializedWithEmptyString)).isEqualTo(tokenWithNull);
+ }
+
+ /**
+ * See {@link com.google.gerrit.server.cache.testing.SerializedClassSubject} for background and
+ * what to do if this test fails.
+ */
+ @Test
+ public void oAuthTokenFields() throws Exception {
+ assertThatSerializedClass(OAuthToken.class)
+ .hasFields(
+ ImmutableMap.<String, Type>builder()
+ .put("token", String.class)
+ .put("secret", String.class)
+ .put("raw", String.class)
+ .put("expiresAt", long.class)
+ .put("providerId", String.class)
+ .build());
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/BUILD b/javatests/com/google/gerrit/server/cache/BUILD
index eed4a87..b173957 100644
--- a/javatests/com/google/gerrit/server/cache/BUILD
+++ b/javatests/com/google/gerrit/server/cache/BUILD
@@ -6,7 +6,9 @@
deps = [
"//java/com/google/gerrit/server",
"//lib:guava",
+ "//lib:gwtorm",
"//lib:junit",
+ "//lib:protobuf",
"//lib:truth",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
diff --git a/javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java
new file mode 100644
index 0000000..3186620
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/BooleanCacheSerializerTest.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.protobuf.TextFormat;
+import org.junit.Test;
+
+public class BooleanCacheSerializerTest {
+ @Test
+ public void serialize() throws Exception {
+ assertThat(BooleanCacheSerializer.INSTANCE.serialize(true))
+ .isEqualTo(new byte[] {'t', 'r', 'u', 'e'});
+ assertThat(BooleanCacheSerializer.INSTANCE.serialize(false))
+ .isEqualTo(new byte[] {'f', 'a', 'l', 's', 'e'});
+ }
+
+ @Test
+ public void deserialize() throws Exception {
+ assertThat(BooleanCacheSerializer.INSTANCE.deserialize(new byte[] {'t', 'r', 'u', 'e'}))
+ .isEqualTo(true);
+ assertThat(BooleanCacheSerializer.INSTANCE.deserialize(new byte[] {'f', 'a', 'l', 's', 'e'}))
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void deserializeInvalid() throws Exception {
+ assertDeserializeFails(null);
+ assertDeserializeFails("t".getBytes(UTF_8));
+ assertDeserializeFails("tru".getBytes(UTF_8));
+ assertDeserializeFails("trueee".getBytes(UTF_8));
+ assertDeserializeFails("TRUE".getBytes(UTF_8));
+ assertDeserializeFails("f".getBytes(UTF_8));
+ assertDeserializeFails("fal".getBytes(UTF_8));
+ assertDeserializeFails("falseee".getBytes(UTF_8));
+ assertDeserializeFails("FALSE".getBytes(UTF_8));
+ }
+
+ private static void assertDeserializeFails(byte[] in) {
+ try {
+ BooleanCacheSerializer.INSTANCE.deserialize(in);
+ assert_().fail("expected deserialization to fail for \"%s\"", TextFormat.escapeBytes(in));
+ } catch (RuntimeException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
index 0e04d32..60bbb16 100644
--- a/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
@@ -15,6 +15,8 @@
package com.google.gerrit.server.cache;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static java.nio.charset.StandardCharsets.UTF_8;
import org.junit.Test;
@@ -26,6 +28,14 @@
assertRoundTrip(MyEnum.BAZ);
}
+ @Test
+ public void deserializeInvalidValues() throws Exception {
+ assertDeserializeFails(null);
+ assertDeserializeFails("".getBytes(UTF_8));
+ assertDeserializeFails("foo".getBytes(UTF_8));
+ assertDeserializeFails("QUUX".getBytes(UTF_8));
+ }
+
private enum MyEnum {
FOO,
BAR,
@@ -36,4 +46,14 @@
CacheSerializer<MyEnum> s = new EnumCacheSerializer<>(MyEnum.class);
assertThat(s.deserialize(s.serialize(e))).isEqualTo(e);
}
+
+ private static void assertDeserializeFails(byte[] in) {
+ CacheSerializer<MyEnum> s = new EnumCacheSerializer<>(MyEnum.class);
+ try {
+ s.deserialize(in);
+ assert_().fail("expected RuntimeException");
+ } catch (RuntimeException e) {
+ // Expected.
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java
new file mode 100644
index 0000000..7a7c27c
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/IntKeyCacheSerializerTest.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.gwtorm.client.IntKey;
+import com.google.gwtorm.client.Key;
+import org.junit.Test;
+
+public class IntKeyCacheSerializerTest {
+
+ private static class MyIntKey extends IntKey<Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ private int val;
+
+ MyIntKey(int val) {
+ this.val = val;
+ }
+
+ @Override
+ public int get() {
+ return val;
+ }
+
+ @Override
+ protected void set(int newValue) {
+ this.val = newValue;
+ }
+ }
+
+ private static final IntKeyCacheSerializer<MyIntKey> SERIALIZER =
+ new IntKeyCacheSerializer<>(MyIntKey::new);
+
+ @Test
+ public void serialize() throws Exception {
+ MyIntKey k = new MyIntKey(1234);
+ byte[] serialized = SERIALIZER.serialize(k);
+ assertThat(serialized).isEqualTo(new byte[] {-46, 9});
+ assertThat(SERIALIZER.deserialize(serialized).get()).isEqualTo(1234);
+ }
+
+ @Test
+ public void deserializeNullFails() throws Exception {
+ try {
+ SERIALIZER.deserialize(null);
+ assert_().fail("expected RuntimeException");
+ } catch (RuntimeException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java
new file mode 100644
index 0000000..962b797
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/IntegerCacheSerializerTest.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2018 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.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Bytes;
+import com.google.protobuf.TextFormat;
+import org.junit.Test;
+
+public class IntegerCacheSerializerTest {
+ @Test
+ public void serialize() throws Exception {
+ for (int i :
+ ImmutableList.of(
+ Integer.MIN_VALUE,
+ Integer.MIN_VALUE + 20,
+ -1,
+ 0,
+ 1,
+ Integer.MAX_VALUE - 20,
+ Integer.MAX_VALUE)) {
+ assertRoundTrip(i);
+ }
+ }
+
+ @Test
+ public void deserializeInvalidValues() throws Exception {
+ assertDeserializeFails(null);
+ assertDeserializeFails(
+ Bytes.concat(IntegerCacheSerializer.INSTANCE.serialize(1), new byte[] {0, 0, 0, 0}));
+ }
+
+ private static void assertRoundTrip(int i) throws Exception {
+ byte[] serialized = IntegerCacheSerializer.INSTANCE.serialize(i);
+ int result = IntegerCacheSerializer.INSTANCE.deserialize(serialized);
+ assertThat(result)
+ .named("round-trip of %s via \"%s\"", i, TextFormat.escapeBytes(serialized))
+ .isEqualTo(i);
+ }
+
+ private static void assertDeserializeFails(byte[] in) {
+ try {
+ IntegerCacheSerializer.INSTANCE.deserialize(in);
+ assert_().fail("expected RuntimeException");
+ } catch (RuntimeException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
index 4470f55..5b77094 100644
--- a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
+++ b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
@@ -15,10 +15,12 @@
package com.google.gerrit.server.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
-import com.google.protobuf.ByteString;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -45,11 +47,15 @@
assertThat(s.deserialize(serialized)).isEqualTo(key);
}
- private static ByteString bytes(int... ints) {
- byte[] bytes = new byte[ints.length];
- for (int i = 0; i < ints.length; i++) {
- bytes[i] = (byte) ints[i];
- }
- return ByteString.copyFrom(bytes);
+ /**
+ * See {@link com.google.gerrit.server.cache.testing.SerializedClassSubject} for background and
+ * what to do if this test fails.
+ */
+ @Test
+ public void keyFields() throws Exception {
+ assertThatSerializedClass(ChangeKindCacheImpl.Key.class)
+ .hasFields(
+ ImmutableMap.of(
+ "prior", ObjectId.class, "next", ObjectId.class, "strategyName", String.class));
}
}
diff --git a/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
new file mode 100644
index 0000000..69fc531
--- /dev/null
+++ b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.bytes;
+import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class MergeabilityCacheImplTest {
+ @Test
+ public void keySerializer() throws Exception {
+ MergeabilityCacheImpl.EntryKey key =
+ new MergeabilityCacheImpl.EntryKey(
+ ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee"),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
+ SubmitType.MERGE_IF_NECESSARY,
+ "aStrategy");
+ byte[] serialized = MergeabilityCacheImpl.EntryKey.Serializer.INSTANCE.serialize(key);
+ assertThat(MergeabilityKeyProto.parseFrom(serialized))
+ .isEqualTo(
+ MergeabilityKeyProto.newBuilder()
+ .setCommit(
+ bytes(
+ 0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee,
+ 0xba, 0xdc, 0x0f, 0xee, 0xba, 0xdc, 0x0f, 0xee))
+ .setInto(
+ bytes(
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
+ .setSubmitType("MERGE_IF_NECESSARY")
+ .setMergeStrategy("aStrategy")
+ .build());
+ assertThat(MergeabilityCacheImpl.EntryKey.Serializer.INSTANCE.deserialize(serialized))
+ .isEqualTo(key);
+ }
+
+ /**
+ * See {@link com.google.gerrit.server.cache.testing.SerializedClassSubject} for background and
+ * what to do if this test fails.
+ */
+ @Test
+ public void keyFields() throws Exception {
+ assertThatSerializedClass(MergeabilityCacheImpl.EntryKey.class)
+ .hasFields(
+ ImmutableMap.of(
+ "commit", ObjectId.class,
+ "into", ObjectId.class,
+ "submitType", SubmitType.class,
+ "mergeStrategy", String.class));
+ }
+}
diff --git a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
index 935dfc6..fd9c925 100644
--- a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
+++ b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -87,7 +87,7 @@
}
@Override
- public WithUser absentUser(Id user) {
+ public WithUser absentUser(Id id) {
throw new UnsupportedOperationException();
}
diff --git a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
index d242962..834f658 100644
--- a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
+++ b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
@@ -78,6 +78,11 @@
}
@Override
+ public ForProject absentUser(Account.Id id) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
public ForRef ref(String ref) {
throw new UnsupportedOperationException("not implemented");
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
index 9b7aad2..d974877 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -442,17 +442,17 @@
// Change created in WIP remains in WIP.
RevCommit commit = writeCommit("Update WIP change\n" + "\n" + "Patch-set: 1\n", true);
ChangeNotesState state = newParser(commit).parseAll();
- assertThat(state.hasReviewStarted()).isFalse();
+ assertThat(state.columns().hasReviewStarted()).isFalse();
// Moving change out of WIP starts review.
commit =
writeCommit("New ready change\n" + "\n" + "Patch-set: 1\n" + "Work-in-progress: false\n");
state = newParser(commit).parseAll();
- assertThat(state.hasReviewStarted()).isTrue();
+ assertThat(state.columns().hasReviewStarted()).isTrue();
// Change created not in WIP has always been in review started state.
state = assertParseSucceeds("New change that doesn't declare WIP\n" + "\n" + "Patch-set: 1\n");
- assertThat(state.hasReviewStarted()).isTrue();
+ assertThat(state.columns().hasReviewStarted()).isTrue();
}
@Test
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index e5a34aa..9d38704 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -1078,7 +1078,6 @@
ChangeNotes notes = newNotes(c);
assertThat(notes.getPatchSets().keySet()).containsExactly(psId1, psId2);
assertThat(notes.getApprovals()).isNotEmpty();
- assertThat(notes.getChangeMessagesByPatchSet()).isNotEmpty();
assertThat(notes.getChangeMessages()).isNotEmpty();
assertThat(notes.getComments()).isNotEmpty();
@@ -1095,7 +1094,6 @@
notes = newNotes(c);
assertThat(notes.getPatchSets().keySet()).containsExactly(psId1);
assertThat(notes.getApprovals()).isEmpty();
- assertThat(notes.getChangeMessagesByPatchSet()).isEmpty();
assertThat(notes.getChangeMessages()).isEmpty();
assertThat(notes.getComments()).isEmpty();
}
@@ -1349,16 +1347,12 @@
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.setChangeMessage("Just a little code change.\n");
update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = notes.getChangeMessagesByPatchSet();
- assertThat(changeMessages.keySet()).containsExactly(ps1);
-
- ChangeMessage cm = Iterables.getOnlyElement(changeMessages.get(ps1));
+ ChangeMessage cm = Iterables.getOnlyElement(notes.getChangeMessages());
assertThat(cm.getMessage()).isEqualTo("Just a little code change.\n");
assertThat(cm.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
- assertThat(cm.getPatchSetId()).isEqualTo(ps1);
+ assertThat(cm.getPatchSetId()).isEqualTo(c.currentPatchSetId());
}
@Test
@@ -1378,13 +1372,9 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeMessage("Testing trailing double newline\n\n");
update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = notes.getChangeMessagesByPatchSet();
- assertThat(changeMessages).hasSize(1);
-
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ ChangeMessage cm1 = Iterables.getOnlyElement(notes.getChangeMessages());
assertThat(cm1.getMessage()).isEqualTo("Testing trailing double newline\n\n");
assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
}
@@ -1395,13 +1385,9 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeMessage("Testing paragraph 1\n\nTesting paragraph 2\n\nTesting paragraph 3");
update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = notes.getChangeMessagesByPatchSet();
- assertThat(changeMessages).hasSize(1);
-
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ ChangeMessage cm1 = Iterables.getOnlyElement(notes.getChangeMessages());
assertThat(cm1.getMessage())
.isEqualTo(
"Testing paragraph 1\n"
@@ -1429,15 +1415,15 @@
PatchSet.Id ps2 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = notes.getChangeMessagesByPatchSet();
- assertThat(changeMessages).hasSize(2);
+ assertThat(notes.getChangeMessages()).hasSize(2);
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ ChangeMessage cm1 = notes.getChangeMessages().get(0);
+ assertThat(cm1.getPatchSetId()).isEqualTo(ps1);
assertThat(cm1.getMessage()).isEqualTo("This is the change message for the first PS.");
assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
- ChangeMessage cm2 = Iterables.getOnlyElement(changeMessages.get(ps2));
- assertThat(cm1.getPatchSetId()).isEqualTo(ps1);
+ ChangeMessage cm2 = notes.getChangeMessages().get(1);
+ assertThat(cm2.getPatchSetId()).isEqualTo(ps2);
assertThat(cm2.getMessage()).isEqualTo("This is the change message for the second PS.");
assertThat(cm2.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
assertThat(cm2.getPatchSetId()).isEqualTo(ps2);
@@ -1459,10 +1445,8 @@
update.commit();
ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = notes.getChangeMessagesByPatchSet();
- assertThat(changeMessages.keySet()).hasSize(1);
- List<ChangeMessage> cm = changeMessages.get(ps1);
+ List<ChangeMessage> cm = notes.getChangeMessages();
assertThat(cm).hasSize(2);
assertThat(cm.get(0).getMessage()).isEqualTo("First change message.\n");
assertThat(cm.get(0).getAuthor()).isEqualTo(changeOwner.getAccount().getId());
@@ -3266,7 +3250,7 @@
public void privateDefault() throws Exception {
Change c = newChange();
ChangeNotes notes = newNotes(c);
- assertThat(notes.isPrivate()).isFalse();
+ assertThat(notes.getChange().isPrivate()).isFalse();
}
@Test
@@ -3277,7 +3261,7 @@
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.isPrivate()).isTrue();
+ assertThat(notes.getChange().isPrivate()).isTrue();
}
@Test
@@ -3292,7 +3276,7 @@
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.isPrivate()).isFalse();
+ assertThat(notes.getChange().isPrivate()).isFalse();
}
@Test
@@ -3397,38 +3381,38 @@
@Test
public void hasReviewStarted() throws Exception {
ChangeNotes notes = newNotes(newChange());
- assertThat(notes.hasReviewStarted()).isTrue();
+ assertThat(notes.getChange().hasReviewStarted()).isTrue();
notes = newNotes(newWorkInProgressChange());
- assertThat(notes.hasReviewStarted()).isFalse();
+ assertThat(notes.getChange().hasReviewStarted()).isFalse();
Change c = newWorkInProgressChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.commit();
notes = newNotes(c);
- assertThat(notes.hasReviewStarted()).isFalse();
+ assertThat(notes.getChange().hasReviewStarted()).isFalse();
update = newUpdate(c, changeOwner);
update.setWorkInProgress(true);
update.commit();
notes = newNotes(c);
- assertThat(notes.hasReviewStarted()).isFalse();
+ assertThat(notes.getChange().hasReviewStarted()).isFalse();
update = newUpdate(c, changeOwner);
update.setWorkInProgress(false);
update.commit();
notes = newNotes(c);
- assertThat(notes.hasReviewStarted()).isTrue();
+ assertThat(notes.getChange().hasReviewStarted()).isTrue();
// Once review is started, setting WIP should have no impact.
c = newChange();
notes = newNotes(c);
- assertThat(notes.hasReviewStarted()).isTrue();
+ assertThat(notes.getChange().hasReviewStarted()).isTrue();
update = newUpdate(c, changeOwner);
update.setWorkInProgress(true);
update.commit();
notes = newNotes(c);
- assertThat(notes.hasReviewStarted()).isTrue();
+ assertThat(notes.getChange().hasReviewStarted()).isTrue();
}
@Test
@@ -3493,7 +3477,7 @@
public void revertOfIsNullByDefault() throws Exception {
Change c = newChange();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getRevertOf()).isNull();
+ assertThat(notes.getChange().getRevertOf()).isNull();
}
@Test
@@ -3504,7 +3488,7 @@
update.setChangeId(c.getKey().get());
update.setRevertOf(changeToRevert.getId().get());
update.commit();
- assertThat(newNotes(c).getRevertOf()).isEqualTo(changeToRevert.getId());
+ assertThat(newNotes(c).getChange().getRevertOf()).isEqualTo(changeToRevert.getId());
}
@Test
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index c30803a..7890de8 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -47,6 +47,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
@@ -202,6 +203,7 @@
@Inject private InMemoryDatabase schemaFactory;
@Inject private ThreadLocalRequestContext requestContext;
@Inject private DefaultRefFilter.Factory refFilterFactory;
+ @Inject private IdentifiedUser.GenericFactory identifiedUserFactory;
@Before
public void setUp() throws Exception {
@@ -986,6 +988,7 @@
changeControlFactory,
permissionBackend,
refFilterFactory,
+ identifiedUserFactory,
new MockUser(name, memberOf),
newProjectState(local));
}
diff --git a/lib/guava.bzl b/lib/guava.bzl
index db85900..e90c2b3 100644
--- a/lib/guava.bzl
+++ b/lib/guava.bzl
@@ -1,5 +1,5 @@
-GUAVA_VERSION = "24.1-jre"
+GUAVA_VERSION = "25.0-jre"
-GUAVA_BIN_SHA1 = "96c528475465aeb22cce60605d230a7e67cebd7b"
+GUAVA_BIN_SHA1 = "7319c34fa5866a85b6bad445adad69d402323129"
GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
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 b59742e..3f26628 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
@@ -283,6 +283,7 @@
as="file"
initial-count="[[fileListIncrement]]"
target-framerate="1">
+ [[_reportRenderedRow(index)]]
<div class="stickyArea">
<div class$="file-row row [[_computePathClass(file.__path, _expandedFilePaths.*)]]"
data-path$="[[file.__path]]" tabindex="-1">
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 0fa037c..c1eb39c 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
@@ -26,6 +26,8 @@
const SIZE_BAR_GAP_WIDTH = 1;
const SIZE_BAR_MIN_WIDTH = 1.5;
+ const RENDER_TIME = 'FileListRenderTime';
+
const FileStatus = {
A: 'Added',
C: 'Copied',
@@ -797,6 +799,12 @@
_computeFilesShown(numFilesShown, files) {
const filesShown = files.base.slice(0, numFilesShown);
this.fire('files-shown-changed', {length: filesShown.length});
+
+ // Start the timer for the rendering work hwere because this is where the
+ // _shownFiles property is being set, and _shownFiles is used in the
+ // dom-repeat binding.
+ this.$.reporting.time(RENDER_TIME);
+
return filesShown;
},
@@ -1175,5 +1183,21 @@
_noDiffsExpanded() {
return this.filesExpanded === GrFileListConstants.FilesExpandedState.NONE;
},
+
+ /**
+ * Method to call via binding when each file list row is rendered. This
+ * allows approximate detection of when the dom-repeat has completed
+ * rendering.
+ * @param {number} index The index of the row being rendered.
+ * @return {string} an empty string.
+ */
+ _reportRenderedRow(index) {
+ if (index === this._shownFiles.length - 1) {
+ this.async(() => {
+ this.$.reporting.timeEnd(RENDER_TIME);
+ }, 1);
+ }
+ return '';
+ },
});
})();
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 8541edf..0e0a39e 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
@@ -127,6 +127,19 @@
assert.isTrue(controlRow.classList.contains('invisible'));
});
+ test('rendering each row calls the _reportRenderedRow method', () => {
+ const renderedStub = sandbox.stub(element, '_reportRenderedRow');
+ element._filesByPath = _.range(10)
+ .reduce((_filesByPath, i) => {
+ _filesByPath['/file' + i] = {lines_inserted: 9};
+ return _filesByPath;
+ }, {});
+ flushAsynchronousOperations();
+ assert.equal(
+ Polymer.dom(element.root).querySelectorAll('.file-row').length, 10);
+ assert.equal(renderedStub.callCount, 10);
+ });
+
test('calculate totals for patch number', () => {
element._filesByPath = {
'/COMMIT_MSG': {
diff --git a/proto/cache.proto b/proto/cache.proto
index 4a84ab1..634b595 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -25,3 +25,23 @@
bytes next = 2;
string strategy_name = 3;
}
+
+// Serialized form of
+// com.google.gerrit.server.change.MergeabilityCacheImpl.EntryKey.
+// Next ID: 5
+message MergeabilityKeyProto {
+ bytes commit = 1;
+ bytes into = 2;
+ string submit_type = 3;
+ string merge_strategy = 4;
+}
+
+// Serialized form of com.google.gerrit.extensions.auth.oauth.OAuthToken.
+// Next ID: 6
+message OAuthTokenProto {
+ string token = 1;
+ string secret = 2;
+ string raw = 3;
+ int64 expires_at = 4;
+ string provider_id = 5;
+}