Merge "Remove error message from event_label."
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 685f9d9..94db15e 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3811,7 +3811,6 @@
The `CommentLinkInput` entity describes the input for a
link:config-gerrit.html#commentlink[commentlink].
-|==================================================
[options="header",cols="1,^2,4"]
|==================================================
|Field Name | |Description
diff --git a/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java b/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
index 91fbf9e..fe845c0 100644
--- a/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
@@ -303,6 +303,7 @@
return pluginInfoFromChangeInfo(changeInfos.get(0));
}
+ @Nullable
protected static List<PluginDefinedInfo> pluginInfoFromChangeInfo(ChangeInfo changeInfo) {
List<PluginDefinedInfo> pluginInfo = changeInfo.plugins;
if (pluginInfo == null) {
@@ -331,6 +332,7 @@
* @param plugins list of {@code MyInfo} objects, each as a raw map returned from Gson.
* @return decoded list of {@code MyInfo}s.
*/
+ @Nullable
protected static List<PluginDefinedInfo> decodeRawPluginsList(
Gson gson, @Nullable Object plugins) {
if (plugins == null) {
diff --git a/java/com/google/gerrit/acceptance/HttpResponse.java b/java/com/google/gerrit/acceptance/HttpResponse.java
index 88079a4..76c0f04 100644
--- a/java/com/google/gerrit/acceptance/HttpResponse.java
+++ b/java/com/google/gerrit/acceptance/HttpResponse.java
@@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
@@ -59,6 +60,7 @@
return getHeader("X-FYI-Content-Type");
}
+ @Nullable
public String getHeader(String name) {
Header hdr = response.getFirstHeader(name);
return hdr != null ? hdr.getValue() : null;
diff --git a/java/com/google/gerrit/acceptance/config/BUILD b/java/com/google/gerrit/acceptance/config/BUILD
index a8ccc1f..0da68b0 100644
--- a/java/com/google/gerrit/acceptance/config/BUILD
+++ b/java/com/google/gerrit/acceptance/config/BUILD
@@ -7,6 +7,7 @@
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//lib:guava",
"//lib:jgit",
"//lib/auto:auto-value",
diff --git a/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java b/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
index 24a2117..27ce857 100644
--- a/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
+++ b/java/com/google/gerrit/acceptance/config/ConfigAnnotationParser.java
@@ -18,6 +18,7 @@
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -27,6 +28,7 @@
public class ConfigAnnotationParser {
private static Splitter splitter = Splitter.on(".").trimResults();
+ @Nullable
public static Config parse(Config base, GerritConfigs annotation) {
if (annotation == null) {
return null;
@@ -45,6 +47,7 @@
return cfg;
}
+ @Nullable
public static Map<String, Config> parse(GlobalPluginConfigs annotation) {
if (annotation == null || annotation.value().length < 1) {
return null;
@@ -67,6 +70,7 @@
return result;
}
+ @Nullable
public static Map<String, Config> parse(GlobalPluginConfig annotation) {
if (annotation == null) {
return null;
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/BUILD b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
index 850a133..d34b79a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/BUILD
+++ b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
@@ -8,6 +8,7 @@
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/acceptance:function",
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index deeb843..b1cd506 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -26,6 +26,7 @@
import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestCapability;
import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestLabelPermission;
import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestPermission;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupReference;
@@ -213,6 +214,7 @@
as -> as.upsertPermission(key.name()).setExclusiveGroup(exclusive)));
}
+ @Nullable
private RevCommit headOrNull(String branch) {
branch = RefNames.fullName(branch);
diff --git a/java/com/google/gerrit/auth/ldap/FakeLdapGroupBackend.java b/java/com/google/gerrit/auth/ldap/FakeLdapGroupBackend.java
index 8fb4d35..c40baba 100644
--- a/java/com/google/gerrit/auth/ldap/FakeLdapGroupBackend.java
+++ b/java/com/google/gerrit/auth/ldap/FakeLdapGroupBackend.java
@@ -39,6 +39,7 @@
return uuid.get().startsWith(LDAP_UUID);
}
+ @Nullable
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
if (!handles(uuid)) {
diff --git a/java/com/google/gerrit/auth/ldap/Helper.java b/java/com/google/gerrit/auth/ldap/Helper.java
index a939c72..c11d045 100644
--- a/java/com/google/gerrit/auth/ldap/Helper.java
+++ b/java/com/google/gerrit/auth/ldap/Helper.java
@@ -18,6 +18,7 @@
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.metrics.Description;
@@ -224,6 +225,7 @@
return ctx;
}
+ @Nullable
private DirContext kerberosOpen(Properties env)
throws IOException, LoginException, NamingException {
LoginContext ctx = new LoginContext("KerberosLogin");
diff --git a/java/com/google/gerrit/auth/ldap/LdapGroupBackend.java b/java/com/google/gerrit/auth/ldap/LdapGroupBackend.java
index c3870f4..bb6480a 100644
--- a/java/com/google/gerrit/auth/ldap/LdapGroupBackend.java
+++ b/java/com/google/gerrit/auth/ldap/LdapGroupBackend.java
@@ -117,6 +117,7 @@
return isLdapUUID(uuid);
}
+ @Nullable
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
if (!handles(uuid)) {
diff --git a/java/com/google/gerrit/auth/ldap/LdapQuery.java b/java/com/google/gerrit/auth/ldap/LdapQuery.java
index 409c9f5..71dc141 100644
--- a/java/com/google/gerrit/auth/ldap/LdapQuery.java
+++ b/java/com/google/gerrit/auth/ldap/LdapQuery.java
@@ -14,6 +14,7 @@
package com.google.gerrit.auth.ldap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.metrics.Timer0;
import java.util.ArrayList;
@@ -114,6 +115,7 @@
return get("dn");
}
+ @Nullable
String get(String attName) throws NamingException {
final Attribute att = getAll(attName);
return att != null && 0 < att.size() ? String.valueOf(att.get(0)) : null;
diff --git a/java/com/google/gerrit/auth/ldap/LdapRealm.java b/java/com/google/gerrit/auth/ldap/LdapRealm.java
index 7699799..7dc2b1b 100644
--- a/java/com/google/gerrit/auth/ldap/LdapRealm.java
+++ b/java/com/google/gerrit/auth/ldap/LdapRealm.java
@@ -20,6 +20,7 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -162,6 +163,7 @@
return vlist;
}
+ @Nullable
static String optdef(Config c, String n, String d) {
final String[] v = c.getStringList("ldap", null, n);
if (v == null || v.length == 0) {
@@ -184,6 +186,7 @@
return v;
}
+ @Nullable
static ParameterizedString paramString(Config c, String n, String d) {
String expression = optdef(c, n, d);
if (expression == null) {
@@ -209,6 +212,7 @@
return !readOnlyAccountFields.contains(field);
}
+ @Nullable
static String apply(ParameterizedString p, LdapQuery.Result m) throws NamingException {
if (p == null) {
return null;
@@ -306,6 +310,7 @@
usernameCache.put(who.getLocalUser(), Optional.of(account.id()));
}
+ @Nullable
@Override
public Account.Id lookup(String accountName) {
if (Strings.isNullOrEmpty(accountName)) {
diff --git a/java/com/google/gerrit/auth/oauth/OAuthTokenCache.java b/java/com/google/gerrit/auth/oauth/OAuthTokenCache.java
index b0c1f51..ab53cde 100644
--- a/java/com/google/gerrit/auth/oauth/OAuthTokenCache.java
+++ b/java/com/google/gerrit/auth/oauth/OAuthTokenCache.java
@@ -20,6 +20,7 @@
import com.google.common.base.Converter;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
@@ -109,6 +110,7 @@
this.encrypter = encrypter;
}
+ @Nullable
public OAuthToken get(Account.Id id) {
OAuthToken accessToken = cache.getIfPresent(id);
if (accessToken == null) {
diff --git a/java/com/google/gerrit/common/data/GlobalCapability.java b/java/com/google/gerrit/common/data/GlobalCapability.java
index 253266d..0a42d09 100644
--- a/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -14,6 +14,7 @@
package com.google.gerrit.common.data;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.PermissionRange;
import java.util.ArrayList;
@@ -190,6 +191,7 @@
}
/** Returns the valid range for the capability if it has one, otherwise null. */
+ @Nullable
public static PermissionRange.WithDefaults getRange(String varName) {
if (QUERY_LIMIT.equalsIgnoreCase(varName)) {
return new PermissionRange.WithDefaults(
diff --git a/java/com/google/gerrit/entities/Account.java b/java/com/google/gerrit/entities/Account.java
index 303e79f..93a4408 100644
--- a/java/com/google/gerrit/entities/Account.java
+++ b/java/com/google/gerrit/entities/Account.java
@@ -58,6 +58,7 @@
return Optional.ofNullable(Ints.tryParse(str)).map(Account::id);
}
+ @Nullable
public static Id fromRef(String name) {
if (name == null) {
return null;
@@ -78,11 +79,13 @@
* @param name a ref name with the following syntax: {@code "34/1234..."}. We assume that the
* caller has trimmed any prefix.
*/
+ @Nullable
public static Id fromRefPart(String name) {
Integer id = RefNames.parseShardedRefPart(name);
return id != null ? Account.id(id) : null;
}
+ @Nullable
public static Id parseAfterShardedRefPart(String name) {
Integer id = RefNames.parseAfterShardedRefPart(name);
return id != null ? Account.id(id) : null;
@@ -98,6 +101,7 @@
* @param name ref name
* @return account ID, or null if not numeric.
*/
+ @Nullable
public static Id fromRefSuffix(String name) {
Integer id = RefNames.parseRefSuffix(name);
return id != null ? Account.id(id) : null;
diff --git a/java/com/google/gerrit/entities/AccountGroup.java b/java/com/google/gerrit/entities/AccountGroup.java
index 001a544..b5c97da 100644
--- a/java/com/google/gerrit/entities/AccountGroup.java
+++ b/java/com/google/gerrit/entities/AccountGroup.java
@@ -15,6 +15,7 @@
package com.google.gerrit.entities;
import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.Nullable;
public final class AccountGroup {
public static NameKey nameKey(String n) {
@@ -65,6 +66,7 @@
}
/** Parse an {@link AccountGroup.UUID} out of a ref-name. */
+ @Nullable
public static UUID fromRef(String ref) {
if (ref == null) {
return null;
@@ -81,6 +83,7 @@
* @param refPart a ref name with the following syntax: {@code "12/1234..."}. We assume that the
* caller has trimmed any prefix.
*/
+ @Nullable
public static UUID fromRefPart(String refPart) {
String uuid = RefNames.parseShardedUuidFromRefPart(refPart);
return uuid != null ? AccountGroup.uuid(uuid) : null;
diff --git a/java/com/google/gerrit/entities/Address.java b/java/com/google/gerrit/entities/Address.java
index 5d63476..eb1da46 100644
--- a/java/com/google/gerrit/entities/Address.java
+++ b/java/com/google/gerrit/entities/Address.java
@@ -46,6 +46,7 @@
throw new IllegalArgumentException("Invalid email address: " + in);
}
+ @Nullable
public static Address tryParse(String in) {
try {
return parse(in);
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index 66e1a96..55220f3 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -117,6 +117,7 @@
return id != null ? Optional.of(Change.id(id)) : Optional.empty();
}
+ @Nullable
public static Id fromRef(String ref) {
if (RefNames.isRefsEdit(ref)) {
return fromEditRefPart(ref);
@@ -134,6 +135,7 @@
return null;
}
+ @Nullable
public static Id fromAllUsersRef(String ref) {
if (ref == null) {
return null;
@@ -169,6 +171,7 @@
return true;
}
+ @Nullable
public static Id fromEditRefPart(String ref) {
int startChangeId = ref.indexOf(RefNames.EDIT_PREFIX) + RefNames.EDIT_PREFIX.length();
int endChangeId = nextNonDigit(ref, startChangeId);
@@ -179,6 +182,7 @@
return null;
}
+ @Nullable
public static Id fromRefPart(String ref) {
Integer id = RefNames.parseShardedRefPart(ref);
return id != null ? Change.id(id) : null;
@@ -404,6 +408,7 @@
return changeStatus;
}
+ @Nullable
public static Status forCode(char c) {
for (Status s : Status.values()) {
if (s.code == c) {
@@ -414,6 +419,7 @@
return null;
}
+ @Nullable
public static Status forChangeStatus(ChangeStatus cs) {
for (Status s : Status.values()) {
if (s.changeStatus == cs) {
@@ -599,6 +605,7 @@
}
/** Get the id of the most current {@link PatchSet} in this change. */
+ @Nullable
public PatchSet.Id currentPatchSetId() {
if (currentPatchSetId > 0) {
return PatchSet.id(changeId, currentPatchSetId);
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index 65a1559..f82efc0 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -49,6 +49,7 @@
return code;
}
+ @Nullable
public static Status forCode(char c) {
for (Status s : Status.values()) {
if (s.code == c) {
diff --git a/java/com/google/gerrit/entities/LabelType.java b/java/com/google/gerrit/entities/LabelType.java
index e31c764..f009872 100644
--- a/java/com/google/gerrit/entities/LabelType.java
+++ b/java/com/google/gerrit/entities/LabelType.java
@@ -132,6 +132,7 @@
return psa.labelId().get().equalsIgnoreCase(getName());
}
+ @Nullable
public LabelValue getMin() {
if (getValues().isEmpty()) {
return null;
@@ -139,6 +140,7 @@
return getValues().get(0);
}
+ @Nullable
public LabelValue getMax() {
if (getValues().isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/entities/Patch.java b/java/com/google/gerrit/entities/Patch.java
index 3c33490..bef6580 100644
--- a/java/com/google/gerrit/entities/Patch.java
+++ b/java/com/google/gerrit/entities/Patch.java
@@ -19,6 +19,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import java.util.List;
@@ -112,6 +113,7 @@
}
@UsedAt(UsedAt.Project.COLLABNET)
+ @Nullable
public static ChangeType forCode(char c) {
for (ChangeType s : ChangeType.values()) {
if (s.code == c) {
diff --git a/java/com/google/gerrit/entities/PatchSet.java b/java/com/google/gerrit/entities/PatchSet.java
index 6c52368..3ee90e8 100644
--- a/java/com/google/gerrit/entities/PatchSet.java
+++ b/java/com/google/gerrit/entities/PatchSet.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.InlineMe;
+import com.google.gerrit.common.Nullable;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@@ -83,6 +84,7 @@
}
/** Parse a PatchSet.Id from a {@link #refName()} result. */
+ @Nullable
public static Id fromRef(String ref) {
int cs = Change.Id.startIndex(ref);
if (cs < 0) {
diff --git a/java/com/google/gerrit/entities/Permission.java b/java/com/google/gerrit/entities/Permission.java
index 95164bd..e3e3a57 100644
--- a/java/com/google/gerrit/entities/Permission.java
+++ b/java/com/google/gerrit/entities/Permission.java
@@ -124,6 +124,7 @@
return LABEL_AS + labelName;
}
+ @Nullable
public static String extractLabel(String varName) {
if (isLabel(varName)) {
return varName.substring(LABEL.length());
diff --git a/java/com/google/gerrit/entities/Project.java b/java/com/google/gerrit/entities/Project.java
index 617b827..b587b1d 100644
--- a/java/com/google/gerrit/entities/Project.java
+++ b/java/com/google/gerrit/entities/Project.java
@@ -166,6 +166,7 @@
* @return name key of the parent project, {@code null} if this project is the All-Projects
* project
*/
+ @Nullable
public Project.NameKey getParent(Project.NameKey allProjectsName) {
if (getParent() != null) {
return getParent();
@@ -178,6 +179,7 @@
return allProjectsName;
}
+ @Nullable
public String getParentName() {
return getParent() != null ? getParent().get() : null;
}
diff --git a/java/com/google/gerrit/entities/RefNames.java b/java/com/google/gerrit/entities/RefNames.java
index b9c1b3c..9745fc5 100644
--- a/java/com/google/gerrit/entities/RefNames.java
+++ b/java/com/google/gerrit/entities/RefNames.java
@@ -16,6 +16,7 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import java.util.List;
@@ -222,6 +223,7 @@
return REFS_CACHE_AUTOMERGE + hash.substring(0, 2) + '/' + hash.substring(2);
}
+ @Nullable
public static String shard(int id) {
if (id < 0) {
return null;
@@ -343,6 +345,7 @@
return GERRIT_REFS.stream().anyMatch(internalRef -> ref.startsWith(internalRef));
}
+ @Nullable
static Integer parseShardedRefPart(String name) {
if (name == null) {
return null;
@@ -386,6 +389,7 @@
}
@UsedAt(UsedAt.Project.PLUGINS_ALL)
+ @Nullable
public static String parseShardedUuidFromRefPart(String name) {
if (name == null) {
return null;
@@ -420,6 +424,7 @@
* @return the rest of the name, {@code null} if the ref name part doesn't start with a valid
* sharded ID
*/
+ @Nullable
static String skipShardedRefPart(String name) {
if (name == null) {
return null;
@@ -473,6 +478,7 @@
* ref name part doesn't start with a valid sharded ID or if no valid ID follows the sharded
* ref part
*/
+ @Nullable
static Integer parseAfterShardedRefPart(String name) {
String rest = skipShardedRefPart(name);
if (rest == null || !rest.startsWith("/")) {
@@ -493,6 +499,7 @@
return Integer.parseInt(rest.substring(0, ie));
}
+ @Nullable
public static Integer parseRefSuffix(String name) {
if (name == null) {
return null;
diff --git a/java/com/google/gerrit/extensions/client/Side.java b/java/com/google/gerrit/extensions/client/Side.java
index e077df2..a87b37a 100644
--- a/java/com/google/gerrit/extensions/client/Side.java
+++ b/java/com/google/gerrit/extensions/client/Side.java
@@ -14,10 +14,13 @@
package com.google.gerrit.extensions.client;
+import com.google.gerrit.common.Nullable;
+
public enum Side {
PARENT,
REVISION;
+ @Nullable
public static Side fromShort(short s) {
if (s <= 0) {
return PARENT;
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index ad112d3..24182cc 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -21,6 +21,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.Timestamp;
@@ -147,12 +148,14 @@
}
/** Returns {@code null} if nothing has been added to {@code oldCollection} */
+ @Nullable
private static ImmutableList<?> getAddedForCollection(
Collection<?> oldCollection, Collection<?> newCollection) {
ImmutableList<?> notInOldCollection = getAdditions(oldCollection, newCollection);
return notInOldCollection.isEmpty() ? null : notInOldCollection;
}
+ @Nullable
private static ImmutableList<Object> getAdditions(
Collection<?> oldCollection, Collection<?> newCollection) {
if (oldCollection == null)
@@ -169,6 +172,7 @@
}
/** Returns {@code null} if nothing has been added to {@code oldMap} */
+ @Nullable
private static ImmutableMap<Object, Object> getAddedForMap(Map<?, ?> oldMap, Map<?, ?> newMap) {
ImmutableMap.Builder<Object, Object> additionsBuilder = ImmutableMap.builder();
for (Map.Entry<?, ?> entry : newMap.entrySet()) {
diff --git a/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java b/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
index d8dd1f9..7ed7077 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.registration;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -39,6 +40,7 @@
return new DynamicItem<>(key, find(injector, type), PluginName.GERRIT);
}
+ @Nullable
private static <T> Provider<T> find(Injector src, TypeLiteral<T> type) {
List<Binding<T>> bindings = src.findBindingsByType(type);
if (bindings != null && bindings.size() == 1) {
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index a0b2c6a..6dc8c6a 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -313,6 +314,7 @@
return key;
}
+ @Nullable
@Override
public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
Extension<T> n = new Extension<>(item.getPluginName(), newItem);
diff --git a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
index fb520b4..67fc068 100644
--- a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
+++ b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
@@ -16,6 +16,7 @@
import static java.util.Objects.requireNonNull;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.Export;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -79,6 +80,7 @@
return key;
}
+ @Nullable
@Override
public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
if (items.replace(np, item, newItem)) {
diff --git a/java/com/google/gerrit/extensions/restapi/Url.java b/java/com/google/gerrit/extensions/restapi/Url.java
index 9c69376..09def84 100644
--- a/java/com/google/gerrit/extensions/restapi/Url.java
+++ b/java/com/google/gerrit/extensions/restapi/Url.java
@@ -16,6 +16,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.common.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@@ -40,6 +41,7 @@
* @param component a string containing text to encode.
* @return a string with all invalid URL characters escaped.
*/
+ @Nullable
public static String encode(String component) {
if (component != null) {
try {
@@ -52,6 +54,7 @@
}
/** Decode a URL encoded string, e.g. from {@code "%2F"} to {@code "/"}. */
+ @Nullable
public static String decode(String str) {
if (str != null) {
try {
diff --git a/java/com/google/gerrit/gpg/BUILD b/java/com/google/gerrit/gpg/BUILD
index 0ee5212..b2173c4 100644
--- a/java/com/google/gerrit/gpg/BUILD
+++ b/java/com/google/gerrit/gpg/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
diff --git a/java/com/google/gerrit/gpg/PublicKeyChecker.java b/java/com/google/gerrit/gpg/PublicKeyChecker.java
index 0a96212..5347398 100644
--- a/java/com/google/gerrit/gpg/PublicKeyChecker.java
+++ b/java/com/google/gerrit/gpg/PublicKeyChecker.java
@@ -30,6 +30,7 @@
import static org.bouncycastle.openpgp.PGPSignature.KEY_REVOCATION;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
import java.io.IOException;
import java.time.Instant;
@@ -229,6 +230,7 @@
|| PushCertificateChecker.getCreationTime(revocation).isBefore(now);
}
+ @Nullable
private PGPSignature scanRevocations(
PGPPublicKey key,
Instant now,
@@ -264,6 +266,7 @@
return null;
}
+ @Nullable
private RevocationKey getRevocationKey(PGPPublicKey key, PGPSignature sig) throws PGPException {
if (sig.getKeyID() != key.getKeyID()) {
return null;
@@ -320,6 +323,7 @@
}
}
+ @Nullable
private static RevocationReason getRevocationReason(PGPSignature sig) {
if (sig.getSignatureType() != KEY_REVOCATION) {
throw new IllegalArgumentException(
@@ -425,6 +429,7 @@
return CheckResult.create(OK, problems);
}
+ @Nullable
private static PGPPublicKey getSigner(
PublicKeyStore store,
PGPSignature sig,
@@ -455,6 +460,7 @@
}
}
+ @Nullable
private String checkTrustSubpacket(PGPSignature sig, int depth) {
SignatureSubpacket trustSub =
sig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.TRUST_SIG);
diff --git a/java/com/google/gerrit/gpg/PublicKeyStore.java b/java/com/google/gerrit/gpg/PublicKeyStore.java
index 2cce480..d167ac8 100644
--- a/java/com/google/gerrit/gpg/PublicKeyStore.java
+++ b/java/com/google/gerrit/gpg/PublicKeyStore.java
@@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.ObjectIds;
import java.io.ByteArrayInputStream;
@@ -92,6 +93,7 @@
* null} if none was found.
* @throws PGPException if an error occurred verifying the signature.
*/
+ @Nullable
public static PGPPublicKey getSigner(
Iterable<PGPPublicKeyRing> keyRings, PGPSignature sig, byte[] data) throws PGPException {
for (PGPPublicKeyRing kr : keyRings) {
@@ -126,6 +128,7 @@
* {@code null} if none was found.
* @throws PGPException if an error occurred verifying the certification.
*/
+ @Nullable
public static PGPPublicKey getSigner(
Iterable<PGPPublicKeyRing> keyRings, PGPSignature sig, String userId, PGPPublicKey key)
throws PGPException {
@@ -210,6 +213,7 @@
* @throws PGPException if an error occurred parsing the key data.
* @throws IOException if an error occurred reading the repository data.
*/
+ @Nullable
public PGPPublicKeyRing get(byte[] fingerprint) throws PGPException, IOException {
List<PGPPublicKeyRing> keyRings = get(Fingerprint.getId(fingerprint), fingerprint);
return !keyRings.isEmpty() ? keyRings.get(0) : null;
diff --git a/java/com/google/gerrit/gpg/PushCertificateChecker.java b/java/com/google/gerrit/gpg/PushCertificateChecker.java
index 17ca5a4..b9ff50b 100644
--- a/java/com/google/gerrit/gpg/PushCertificateChecker.java
+++ b/java/com/google/gerrit/gpg/PushCertificateChecker.java
@@ -22,6 +22,7 @@
import com.google.common.base.Joiner;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -176,6 +177,7 @@
return CheckResult.ok();
}
+ @Nullable
private PGPSignature readSignature(PushCertificate cert) throws IOException {
ArmoredInputStream in =
new ArmoredInputStream(new ByteArrayInputStream(Constants.encode(cert.getSignature())));
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 3341806..d51ee6a 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -29,6 +29,7 @@
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
@@ -299,6 +300,7 @@
return externalIdKeyFactory.create(SCHEME_GPGKEY, BaseEncoding.base16().encode(fp));
}
+ @Nullable
private Account getAccountByExternalId(ExternalId.Key extIdKey) {
List<AccountState> accountStates = accountQueryProvider.get().byExternalId(extIdKey);
diff --git a/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 5b62f96..9625039 100644
--- a/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -116,6 +116,7 @@
}
}
+ @Nullable
private static String readCookie(HttpServletRequest request) {
Cookie[] all = request.getCookies();
if (all != null) {
@@ -219,6 +220,7 @@
}
}
+ @Nullable
@Override
public String getSessionId() {
return val != null ? val.getSessionId() : null;
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index fdbe6aa..86c514b 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -667,6 +668,7 @@
public void destroy() {}
}
+ @Nullable
private static String getSessionIdOrNull(Provider<WebSession> sessionProvider) {
WebSession session = sessionProvider.get();
if (session.isSignedIn()) {
diff --git a/java/com/google/gerrit/httpd/HtmlDomUtil.java b/java/com/google/gerrit/httpd/HtmlDomUtil.java
index 57f2664..881df46 100644
--- a/java/com/google/gerrit/httpd/HtmlDomUtil.java
+++ b/java/com/google/gerrit/httpd/HtmlDomUtil.java
@@ -17,6 +17,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.ByteStreams;
+import com.google.gerrit.common.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -89,6 +90,7 @@
}
/** Find an element by its "id" attribute; null if no element is found. */
+ @Nullable
public static Element find(Node parent, String name) {
NodeList list = parent.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
@@ -139,6 +141,7 @@
}
/** Parse an XHTML file from our CLASSPATH and return the instance. */
+ @Nullable
public static Document parseFile(Class<?> context, String name) throws IOException {
try (InputStream in = context.getResourceAsStream(name)) {
if (in == null) {
@@ -168,6 +171,7 @@
}
/** Read a Read a UTF-8 text file from our CLASSPATH and return it. */
+ @Nullable
public static String readFile(Class<?> context, String name) throws IOException {
try (InputStream in = context.getResourceAsStream(name)) {
if (in == null) {
@@ -180,6 +184,7 @@
}
/** Parse an XHTML file from the local drive and return the instance. */
+ @Nullable
public static Document parseFile(Path path) throws IOException {
try (InputStream in = Files.newInputStream(path)) {
Document doc = newBuilder().parse(in);
@@ -193,6 +198,7 @@
}
/** Read a UTF-8 text file from the local drive. */
+ @Nullable
public static String readFile(Path parentDir, String name) throws IOException {
if (parentDir == null) {
return null;
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index de6ae50..5a99cab 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -23,6 +23,7 @@
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -226,6 +227,7 @@
}
}
+ @Nullable
private AuthInfo extractAuthInfo(String hdr, String encoding)
throws UnsupportedEncodingException {
byte[] decoded = BaseEncoding.base64().decode(hdr.substring(BASIC.length()));
@@ -241,6 +243,7 @@
defaultAuthProvider);
}
+ @Nullable
private AuthInfo extractAuthInfo(Cookie cookie) throws UnsupportedEncodingException {
String username =
URLDecoder.decode(cookie.getName().substring(GIT_COOKIE_PREFIX.length()), UTF_8.name());
@@ -272,6 +275,7 @@
return MoreObjects.firstNonNull(req.getCharacterEncoding(), UTF_8.name());
}
+ @Nullable
private static Cookie findGitCookie(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
diff --git a/java/com/google/gerrit/httpd/RemoteUserUtil.java b/java/com/google/gerrit/httpd/RemoteUserUtil.java
index 84954dc..6f3e9c4 100644
--- a/java/com/google/gerrit/httpd/RemoteUserUtil.java
+++ b/java/com/google/gerrit/httpd/RemoteUserUtil.java
@@ -19,6 +19,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.Nullable;
import javax.servlet.http.HttpServletRequest;
public class RemoteUserUtil {
@@ -62,6 +63,7 @@
* @param auth header value which is used for extracting.
* @return username if available or null.
*/
+ @Nullable
public static String extractUsername(String auth) {
auth = emptyToNull(auth);
diff --git a/java/com/google/gerrit/httpd/WebSessionManager.java b/java/com/google/gerrit/httpd/WebSessionManager.java
index 87bf3a6..1137b65 100644
--- a/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -30,6 +30,7 @@
import com.google.common.cache.Cache;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
@@ -149,6 +150,7 @@
return -1;
}
+ @Nullable
Val get(Key key) {
Val val = self.getIfPresent(key.token);
if (val != null && val.expiresAt <= nowMs()) {
diff --git a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 2f760f0..bc8a01a 100644
--- a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -185,6 +186,7 @@
return account.map(a -> new AuthResult(a.account().id(), null, false));
}
+ @Nullable
private AuthResult auth(Account.Id account) {
if (account != null) {
return new AuthResult(account, null, false);
@@ -192,6 +194,7 @@
return null;
}
+ @Nullable
private AuthResult byUserName(String userName) {
List<AccountState> accountStates = queryProvider.get().byExternalId(SCHEME_USERNAME, userName);
if (accountStates.isEmpty()) {
@@ -223,6 +226,7 @@
}
}
+ @Nullable
private AuthResult create() throws IOException {
try {
return accountManager.authenticate(
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index acb3282..be833ea 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -21,6 +21,7 @@
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.RemoteUserUtil;
@@ -143,6 +144,7 @@
: remoteUser;
}
+ @Nullable
String getRemoteDisplayname(HttpServletRequest req) {
if (displaynameHeader != null) {
String raw = req.getHeader(displaynameHeader);
@@ -151,6 +153,7 @@
return null;
}
+ @Nullable
String getRemoteEmail(HttpServletRequest req) {
if (emailHeader != null) {
return emptyToNull(req.getHeader(emailHeader));
@@ -158,6 +161,7 @@
return null;
}
+ @Nullable
String getRemoteExternalIdToken(HttpServletRequest req) {
if (externalIdHeader != null) {
return emptyToNull(req.getHeader(externalIdHeader));
diff --git a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index 0b6008c..e7057ad 100644
--- a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -334,6 +334,7 @@
form.appendChild(div);
}
+ @Nullable
private OAuthServiceProvider lookupOAuthServiceProvider(String providerId) {
if (providerId.startsWith("http://")) {
providerId = providerId.substring("http://".length());
@@ -350,6 +351,7 @@
return null;
}
+ @Nullable
private static String getLastId(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
diff --git a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index fcd16ae..0c71d68 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.auth.openid;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
import com.google.gerrit.entities.Account;
@@ -518,6 +519,7 @@
rsp.sendRedirect(rdr.toString());
}
+ @Nullable
private State init(
HttpServletRequest req,
final String openidIdentifier,
diff --git a/java/com/google/gerrit/httpd/auth/restapi/BUILD b/java/com/google/gerrit/httpd/auth/restapi/BUILD
index d499768..9ab51c5 100644
--- a/java/com/google/gerrit/httpd/auth/restapi/BUILD
+++ b/java/com/google/gerrit/httpd/auth/restapi/BUILD
@@ -6,6 +6,7 @@
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/auth",
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/server",
"//lib/flogger:api",
diff --git a/java/com/google/gerrit/httpd/auth/restapi/GetOAuthToken.java b/java/com/google/gerrit/httpd/auth/restapi/GetOAuthToken.java
index 3594c7c..2eee415 100644
--- a/java/com/google/gerrit/httpd/auth/restapi/GetOAuthToken.java
+++ b/java/com/google/gerrit/httpd/auth/restapi/GetOAuthToken.java
@@ -16,6 +16,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.auth.oauth.OAuthTokenCache;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -70,6 +71,7 @@
return Response.ok(accessTokenInfo);
}
+ @Nullable
private static String getHostName(String canonicalWebUrl) {
if (canonicalWebUrl == null) {
logger.atSevere().log(
diff --git a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index a03aa36..9b8f4c6 100644
--- a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -35,6 +35,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.httpd.resources.ResourceKey;
import com.google.gerrit.httpd.resources.SmallResource;
@@ -174,6 +175,7 @@
plugins.put(name, holder);
}
+ @Nullable
private GuiceFilter load(Plugin plugin) {
if (plugin.getHttpInjector() != null) {
final String name = plugin.getName();
@@ -327,6 +329,7 @@
}
}
+ @Nullable
private static Pattern makeAllowOrigin(Config cfg) {
String[] allow = cfg.getStringList("site", null, "allowOriginRegex");
if (allow.length > 0) {
@@ -720,6 +723,7 @@
this.docPrefix = getPrefix(plugin, "Gerrit-HttpDocumentationPrefix", "Documentation/");
}
+ @Nullable
private static String getPrefix(Plugin plugin, String attr, String def) {
Path path = plugin.getSrcFile();
PluginContentScanner scanner = plugin.getContentScanner();
diff --git a/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java b/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
index fc0ec39..ed29629 100644
--- a/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
@@ -19,6 +19,7 @@
import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.plugins.Plugin;
@@ -119,6 +120,7 @@
filter.set(guiceFilter);
}
+ @Nullable
private GuiceFilter load(Plugin plugin) {
if (plugin.getHttpInjector() != null) {
final String name = plugin.getName();
diff --git a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
index 1c6e058..5cf63d9 100644
--- a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
@@ -106,6 +106,7 @@
ListChangesOption.SKIP_DIFFSTAT,
ListChangesOption.SUBMIT_REQUIREMENTS);
+ @Nullable
public static String getPath(@Nullable String requestedURL) throws URISyntaxException {
if (requestedURL == null) {
return null;
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 15dcf42..a8ff1ff 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -305,6 +305,7 @@
sourceRoot = getSourceRootOrNull();
}
+ @Nullable
private static Path getSourceRootOrNull() {
try {
return GerritLauncher.resolveInSourceRoot(".");
@@ -313,6 +314,7 @@
}
}
+ @Nullable
private FileSystem getDistributionArchive(File war) throws IOException {
if (war == null) {
return null;
@@ -320,6 +322,7 @@
return GerritLauncher.getZipFileSystem(war.toPath());
}
+ @Nullable
private File getLauncherLoadedFrom() {
File war;
try {
@@ -441,6 +444,7 @@
super(req);
}
+ @Nullable
@Override
public String getPathInfo() {
String uri = getRequestURI();
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 543e794..23de6db 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -315,6 +315,7 @@
this.cancellationMetrics = cancellationMetrics;
}
+ @Nullable
private static Pattern makeAllowOrigin(Config cfg) {
String[] allow = cfg.getStringList("site", null, "allowOriginRegex");
if (allow.length > 0) {
@@ -854,6 +855,7 @@
}
}
+ @Nullable
private String getEtagWithRetry(
HttpServletRequest req, TraceContext traceContext, RestResource.HasETag rsrc) {
try (TraceTimer ignored =
@@ -1277,6 +1279,7 @@
return ((ParameterizedType) supertype).getActualTypeArguments()[2];
}
+ @Nullable
private Object parseRequest(HttpServletRequest req, Type type)
throws IOException, BadRequestException, SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InstantiationException,
diff --git a/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java b/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
index 655f4ca..2065a31 100644
--- a/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
+++ b/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
@@ -18,6 +18,7 @@
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
@@ -125,6 +126,7 @@
return cssFile.isStale() || headerFile.isStale() || footerFile.isStale();
}
+ @Nullable
private static Element readXml(FileInfo src) throws IOException {
Document d = HtmlDomUtil.parseFile(src.path);
return d != null ? d.getDocumentElement() : null;
diff --git a/java/com/google/gerrit/index/query/InternalQuery.java b/java/com/google/gerrit/index/query/InternalQuery.java
index 5c003bc..ef69b4c 100644
--- a/java/com/google/gerrit/index/query/InternalQuery.java
+++ b/java/com/google/gerrit/index/query/InternalQuery.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.Index;
@@ -118,6 +119,7 @@
}
}
+ @Nullable
protected final Schema<T> schema() {
Index<?, T> index = indexes != null ? indexes.getSearchIndex() : null;
return index != null ? index.getSchema() : null;
diff --git a/java/com/google/gerrit/index/query/LimitPredicate.java b/java/com/google/gerrit/index/query/LimitPredicate.java
index 23e0f6d..9196811 100644
--- a/java/com/google/gerrit/index/query/LimitPredicate.java
+++ b/java/com/google/gerrit/index/query/LimitPredicate.java
@@ -14,8 +14,11 @@
package com.google.gerrit.index.query;
+import com.google.gerrit.common.Nullable;
+
public class LimitPredicate<T> extends IntPredicate<T> implements Matchable<T> {
@SuppressWarnings("unchecked")
+ @Nullable
public static Integer getLimit(String fieldName, Predicate<?> p) {
IntPredicate<?> ip = QueryBuilder.find(p, IntPredicate.class, fieldName);
return ip != null ? ip.intValue() : null;
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index 086ad90..d18e9c3 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -151,6 +151,7 @@
@Override
public ResultSet<V> read() {
return new ListResultSet<>(results) {
+ @Nullable
@Override
public Object searchAfter() {
@Nullable V last = Iterables.getLast(results, null);
diff --git a/java/com/google/gerrit/json/EnumTypeAdapterFactory.java b/java/com/google/gerrit/json/EnumTypeAdapterFactory.java
index 9c32aa8..b6cb5f9 100644
--- a/java/com/google/gerrit/json/EnumTypeAdapterFactory.java
+++ b/java/com/google/gerrit/json/EnumTypeAdapterFactory.java
@@ -14,6 +14,7 @@
package com.google.gerrit.json;
+import com.google.gerrit.common.Nullable;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
@@ -34,6 +35,7 @@
public class EnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings({"rawtypes", "unchecked"})
+ @Nullable
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
TypeAdapter<T> defaultEnumAdapter = TypeAdapters.ENUM_FACTORY.create(gson, typeToken);
diff --git a/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java b/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java
index d35b8fb..2557515 100644
--- a/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java
+++ b/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java
@@ -45,6 +45,7 @@
TypeToken.get(SubmitRequirementExpressionResult.class);
@SuppressWarnings({"unchecked"})
+ @Nullable
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (typeToken.equals(OPTIONAL_SR_EXPRESSION_RESULT_TOKEN)) {
diff --git a/java/com/google/gerrit/json/SqlTimestampDeserializer.java b/java/com/google/gerrit/json/SqlTimestampDeserializer.java
index e1cf382..9aeda2b 100644
--- a/java/com/google/gerrit/json/SqlTimestampDeserializer.java
+++ b/java/com/google/gerrit/json/SqlTimestampDeserializer.java
@@ -14,6 +14,7 @@
package com.google.gerrit.json;
+import com.google.gerrit.common.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@@ -30,6 +31,7 @@
class SqlTimestampDeserializer implements JsonDeserializer<Timestamp>, JsonSerializer<Timestamp> {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+ @Nullable
@Override
public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java
index a190ebf..6be78d9 100644
--- a/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -533,6 +533,7 @@
return myHome;
}
+ @SuppressWarnings("ReturnMissingNullable")
private static File tmproot() {
File tmp;
String gerritTemp = System.getenv("GERRIT_TMP");
@@ -572,6 +573,7 @@
}
}
+ @SuppressWarnings("ReturnMissingNullable")
private static File locateHomeDirectory() {
// Try to find the user's home directory. If we can't find it
// return null so the JVM's default temporary directory is used
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 755ae7b..079380e 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -33,6 +33,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.converter.ChangeProtoConverter;
@@ -470,6 +471,7 @@
* @param subIndex change sub-index
* @return the score doc that can be used to page result sets
*/
+ @Nullable
private ScoreDoc getSearchAfter(ChangeSubIndex subIndex) {
if (isSearchAfterPagination
&& opts.searchAfter() != null
diff --git a/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index d475ab7..b741042 100644
--- a/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.index.group.GroupField.UUID_FIELD_SPEC;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.exceptions.StorageException;
@@ -151,6 +152,7 @@
new Sort(new SortField(UUID_SORT_FIELD, SortField.Type.STRING, false)));
}
+ @Nullable
@Override
protected InternalGroup fromDocument(Document doc) {
AccountGroup.UUID uuid =
diff --git a/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index 96b22db..6b2b693 100644
--- a/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.index.project.ProjectField.NAME;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.QueryOptions;
@@ -151,6 +152,7 @@
new Sort(new SortField(NAME_SORT_FIELD, SortField.Type.STRING, false)));
}
+ @Nullable
@Override
protected ProjectData fromDocument(Document doc) {
Project.NameKey nameKey = Project.nameKey(doc.getField(NAME.getName()).stringValue());
diff --git a/java/com/google/gerrit/lucene/LuceneStoredValue.java b/java/com/google/gerrit/lucene/LuceneStoredValue.java
index 1f8c039..58ae3e0 100644
--- a/java/com/google/gerrit/lucene/LuceneStoredValue.java
+++ b/java/com/google/gerrit/lucene/LuceneStoredValue.java
@@ -38,6 +38,7 @@
this.field = field;
}
+ @Nullable
@Override
public String asString() {
return Iterables.getFirst(asStrings(), null);
@@ -48,6 +49,7 @@
return field.stream().map(f -> f.stringValue()).collect(toImmutableList());
}
+ @Nullable
@Override
public Integer asInteger() {
return Iterables.getFirst(asIntegers(), null);
@@ -58,6 +60,7 @@
return field.stream().map(f -> f.numericValue().intValue()).collect(toImmutableList());
}
+ @Nullable
@Override
public Long asLong() {
return Iterables.getFirst(asLongs(), null);
@@ -68,11 +71,13 @@
return field.stream().map(f -> f.numericValue().longValue()).collect(toImmutableList());
}
+ @Nullable
@Override
public Timestamp asTimestamp() {
return asLong() == null ? null : new Timestamp(asLong());
}
+ @Nullable
@Override
public byte[] asByteArray() {
return Iterables.getFirst(asByteArrays(), null);
diff --git a/java/com/google/gerrit/lucene/WrappableSearcherManager.java b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
index c164b29..56cb220 100644
--- a/java/com/google/gerrit/lucene/WrappableSearcherManager.java
+++ b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
@@ -17,6 +17,7 @@
* limitations under the License.
*/
+import com.google.gerrit.common.Nullable;
import java.io.IOException;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.FilterDirectoryReader;
@@ -132,6 +133,7 @@
reference.getIndexReader().decRef();
}
+ @Nullable
@Override
protected IndexSearcher refreshIfNeeded(IndexSearcher referenceToRefresh) throws IOException {
final IndexReader r = referenceToRefresh.getIndexReader();
diff --git a/java/com/google/gerrit/metrics/BUILD b/java/com/google/gerrit/metrics/BUILD
index 5f4e0c0..0f80a0c 100644
--- a/java/com/google/gerrit/metrics/BUILD
+++ b/java/com/google/gerrit/metrics/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/lifecycle",
diff --git a/java/com/google/gerrit/metrics/dropwizard/MetricJson.java b/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
index d59a1d9..27e9377 100644
--- a/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
+++ b/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
@@ -23,6 +23,7 @@
import com.codahale.metrics.Timer;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import java.util.ArrayList;
@@ -144,6 +145,7 @@
}
}
+ @Nullable
private static Boolean toBool(ImmutableMap<String, String> atts, String key) {
return Description.TRUE_VALUE.equals(atts.get(key)) ? true : null;
}
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java
index ef0ced6..84f2320 100644
--- a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java
+++ b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java
@@ -15,6 +15,7 @@
package com.google.gerrit.metrics.proc;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.sun.management.UnixOperatingSystemMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
@@ -25,6 +26,7 @@
private OperatingSystemMXBeanFactory() {}
+ @Nullable
static OperatingSystemMXBeanInterface create() {
OperatingSystemMXBean sys = ManagementFactory.getOperatingSystemMXBean();
if (sys instanceof UnixOperatingSystemMXBean) {
diff --git a/java/com/google/gerrit/pgm/SwitchSecureStore.java b/java/com/google/gerrit/pgm/SwitchSecureStore.java
index 824a9a7..6dec2d8 100644
--- a/java/com/google/gerrit/pgm/SwitchSecureStore.java
+++ b/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.SiteLibraryLoaderUtil;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.server.config.SitePaths;
@@ -185,6 +186,7 @@
}
}
+ @Nullable
private Path findJarWithSecureStore(SitePaths sitePaths, String secureStoreClass)
throws IOException {
List<Path> jars = SiteLibraryLoaderUtil.listJars(sitePaths.lib_dir);
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 4592cbb..b59b924 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -22,6 +22,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Die;
import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.IndexType;
import com.google.gerrit.metrics.DisabledMetricMaker;
@@ -175,6 +176,7 @@
*/
protected void afterInit(SiteRun run) throws Exception {}
+ @Nullable
protected List<String> getInstallPlugins() {
try {
if (pluginsToInstall != null && pluginsToInstall.isEmpty()) {
@@ -304,6 +306,7 @@
return ConsoleUI.getInstance(false);
}
+ @Nullable
private SecureStoreInitData discoverSecureStoreClass() {
String secureStore = getSecureStoreLib();
if (Strings.isNullOrEmpty(secureStore)) {
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 4e854b5..3dce974 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -17,6 +17,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.GroupReference;
import com.google.gerrit.entities.InternalGroup;
@@ -182,6 +183,7 @@
return email;
}
+ @Nullable
private AccountSshKey readSshKey(Account.Id id) throws IOException {
String defaultPublicSshKeyFile = "";
Path defaultPublicSshKeyPath = Paths.get(System.getProperty("user.home"), ".ssh", "id_rsa.pub");
diff --git a/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index a7f9c5d..16c4ce7 100644
--- a/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -16,6 +16,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitStep;
@@ -62,6 +63,7 @@
return pluginsInitSteps;
}
+ @Nullable
private InitStep loadInitStep(Path jar) {
try {
URLClassLoader pluginLoader =
diff --git a/java/com/google/gerrit/pgm/init/api/ConsoleUI.java b/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
index dffdde7..7666076 100644
--- a/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
+++ b/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
@@ -17,6 +17,7 @@
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;
import com.google.gerrit.common.Die;
+import com.google.gerrit.common.Nullable;
import java.io.Console;
import java.util.EnumSet;
import java.util.Set;
@@ -179,6 +180,7 @@
@Override
@FormatMethod
+ @Nullable
public String password(String fmt, Object... args) {
final String prompt = String.format(fmt, args);
for (; ; ) {
diff --git a/java/com/google/gerrit/pgm/init/api/InitUtil.java b/java/com/google/gerrit/pgm/init/api/InitUtil.java
index d038de7..7688728 100644
--- a/java/com/google/gerrit/pgm/init/api/InitUtil.java
+++ b/java/com/google/gerrit/pgm/init/api/InitUtil.java
@@ -18,6 +18,7 @@
import com.google.common.io.ByteStreams;
import com.google.gerrit.common.Die;
+import com.google.gerrit.common.Nullable;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -127,6 +128,7 @@
}
}
+ @Nullable
private static InputStream open(Class<?> sibling, String name) {
final InputStream in = sibling.getResourceAsStream(name);
if (in == null) {
diff --git a/java/com/google/gerrit/pgm/init/api/Section.java b/java/com/google/gerrit/pgm/init/api/Section.java
index b5d35f4..5cc4b5d 100644
--- a/java/com/google/gerrit/pgm/init/api/Section.java
+++ b/java/com/google/gerrit/pgm/init/api/Section.java
@@ -166,6 +166,7 @@
return nv;
}
+ @Nullable
public String password(String username, String password) {
final String ov = getSecure(password);
diff --git a/java/com/google/gerrit/pgm/rules/PrologCompiler.java b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
index 0a41db5..de08116 100644
--- a/java/com/google/gerrit/pgm/rules/PrologCompiler.java
+++ b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.rules;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.Version;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -182,6 +183,7 @@
}
}
+ @Nullable
private String getMyClasspath() {
StringBuilder cp = new StringBuilder();
appendClasspath(cp, getClass().getClassLoader());
diff --git a/java/com/google/gerrit/prettify/BUILD b/java/com/google/gerrit/prettify/BUILD
index 0a15fda..a5c8b77 100644
--- a/java/com/google/gerrit/prettify/BUILD
+++ b/java/com/google/gerrit/prettify/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["common/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//lib:guava",
"//lib:jgit",
"//lib/auto:auto-value",
diff --git a/java/com/google/gerrit/prettify/common/SparseFileContent.java b/java/com/google/gerrit/prettify/common/SparseFileContent.java
index 1249b65..f40222a 100644
--- a/java/com/google/gerrit/prettify/common/SparseFileContent.java
+++ b/java/com/google/gerrit/prettify/common/SparseFileContent.java
@@ -17,6 +17,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
/**
* A class to store subset of a file's lines in a memory efficient way. Internally, it stores lines
@@ -134,6 +135,7 @@
return getSize();
}
+ @Nullable
private String getLine(int idx) {
// Most requests are sequential in nature, fetching the next
// line from the current range, or the next range.
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 8198ce4..285657e 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -104,6 +104,7 @@
return PatchSet.id(changeId, comment.key.patchSetId);
}
+ @Nullable
public static String extractMessageId(@Nullable String tag) {
if (tag == null || !tag.startsWith("mailMessageId=")) {
return null;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index cfecd8e..9ac85e0 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -79,6 +79,7 @@
public abstract static class StarField {
private static final String SEPARATOR = ":";
+ @Nullable
public static StarField parse(String s) {
int p = s.indexOf(SEPARATOR);
if (p >= 0) {
diff --git a/java/com/google/gerrit/server/account/AccountLimits.java b/java/com/google/gerrit/server/account/AccountLimits.java
index 5549d28..d97563a 100644
--- a/java/com/google/gerrit/server/account/AccountLimits.java
+++ b/java/com/google/gerrit/server/account/AccountLimits.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.PermissionRange;
import com.google.gerrit.entities.PermissionRule;
@@ -105,6 +106,7 @@
}
/** The range of permitted values associated with a label permission. */
+ @Nullable
public PermissionRange getRange(String permission) {
if (GlobalCapability.hasRange(permission)) {
return toRange(permission, getRules(permission));
diff --git a/java/com/google/gerrit/server/account/AuthRequest.java b/java/com/google/gerrit/server/account/AuthRequest.java
index cceda70..b4fbcdb 100644
--- a/java/com/google/gerrit/server/account/AuthRequest.java
+++ b/java/com/google/gerrit/server/account/AuthRequest.java
@@ -102,6 +102,7 @@
return externalId;
}
+ @Nullable
public String getLocalUser() {
if (externalId.isScheme(SCHEME_GERRIT)) {
return externalId.id();
diff --git a/java/com/google/gerrit/server/account/CreateGroupArgs.java b/java/com/google/gerrit/server/account/CreateGroupArgs.java
index ba58c3f..9d9fe9d 100644
--- a/java/com/google/gerrit/server/account/CreateGroupArgs.java
+++ b/java/com/google/gerrit/server/account/CreateGroupArgs.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import java.util.Collection;
@@ -30,6 +31,7 @@
return groupName;
}
+ @Nullable
public String getGroupName() {
return groupName != null ? groupName.get() : null;
}
diff --git a/java/com/google/gerrit/server/account/DefaultRealm.java b/java/com/google/gerrit/server/account/DefaultRealm.java
index 329825f..cfffceb 100644
--- a/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -16,6 +16,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AccountFieldName;
@@ -79,6 +80,7 @@
@Override
public void onCreateAccount(AuthRequest who, Account account) {}
+ @Nullable
@Override
public Account.Id lookup(String accountName) throws IOException {
if (emailExpander.canExpand(accountName)) {
diff --git a/java/com/google/gerrit/server/account/DestinationList.java b/java/com/google/gerrit/server/account/DestinationList.java
index 15c1e25..084a3ac 100644
--- a/java/com/google/gerrit/server/account/DestinationList.java
+++ b/java/com/google/gerrit/server/account/DestinationList.java
@@ -18,6 +18,7 @@
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
@@ -39,6 +40,7 @@
destinations.replaceValues(label, toSet(parse(text, DIR_NAME + label, TRIM, null, errors)));
}
+ @Nullable
String asText(String label) {
Set<BranchNameKey> dests = destinations.get(label);
if (dests == null) {
diff --git a/java/com/google/gerrit/server/account/GroupCacheImpl.java b/java/com/google/gerrit/server/account/GroupCacheImpl.java
index eaec9ba..2d947ba 100644
--- a/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.entities.RefNames;
@@ -346,6 +347,7 @@
return Protos.toByteArray(InternalGroupSerializer.serialize(value));
}
+ @Nullable
@Override
public InternalGroup deserialize(byte[] in) {
if (Strings.fromByteArray(in).isEmpty()) {
diff --git a/java/com/google/gerrit/server/account/InternalGroupBackend.java b/java/com/google/gerrit/server/account/InternalGroupBackend.java
index 91fe701..01254a0 100644
--- a/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -17,6 +17,7 @@
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
@@ -60,6 +61,7 @@
return ObjectId.isId(uuid.get()); // [0-9a-f]{40};
}
+ @Nullable
@Override
public GroupDescription.Internal get(AccountGroup.UUID uuid) {
if (!handles(uuid)) {
diff --git a/java/com/google/gerrit/server/account/ProjectWatches.java b/java/com/google/gerrit/server/account/ProjectWatches.java
index 42137c1..86132d3 100644
--- a/java/com/google/gerrit/server/account/ProjectWatches.java
+++ b/java/com/google/gerrit/server/account/ProjectWatches.java
@@ -201,6 +201,7 @@
@AutoValue
public abstract static class NotifyValue {
+ @Nullable
public static NotifyValue parse(
Account.Id accountId,
String project,
diff --git a/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index 1587bc5..476ca79 100644
--- a/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -130,6 +130,7 @@
return true;
}
+ @Nullable
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
if (uuid == null) {
diff --git a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index 555a2c1..1fce3d5 100644
--- a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -20,6 +20,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidSshKeyException;
@@ -194,6 +195,7 @@
* @return the SSH key, <code>null</code> if there is no SSH key with this sequence number, or if
* the SSH key with this sequence number has been deleted
*/
+ @Nullable
private AccountSshKey getKey(int seq) {
checkLoaded();
return keys.get(seq - 1).orElse(null);
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index b0618ba..48c403c 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -1020,6 +1020,7 @@
* @return the external ID that was removed, {@code null} if no external ID with the specified key
* exists
*/
+ @Nullable
private ExternalId remove(
RevWalk rw, NoteMap noteMap, ExternalId.Key extIdKey, Account.Id expectedAccountId)
throws IOException, ConfigInvalidException {
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index b23782f..828f868 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import static javax.servlet.http.HttpServletResponse.SC_OK;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.AgreementInput;
@@ -575,6 +576,7 @@
}
}
+ @Nullable
@Override
public String generateHttpPassword() throws RestApiException {
HttpPasswordInput input = new HttpPasswordInput();
@@ -589,6 +591,7 @@
}
}
+ @Nullable
@Override
public String setHttpPassword(String password) throws RestApiException {
HttpPasswordInput input = new HttpPasswordInput();
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 1713171..ac63135 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -583,6 +583,7 @@
}
}
+ @Nullable
@Override
public AccountInfo getAssignee() throws RestApiException {
try {
@@ -602,6 +603,7 @@
}
}
+ @Nullable
@Override
public AccountInfo deleteAssignee() throws RestApiException {
try {
diff --git a/java/com/google/gerrit/server/approval/ApprovalsUtil.java b/java/com/google/gerrit/server/approval/ApprovalsUtil.java
index 8014e17..bd31356f 100644
--- a/java/com/google/gerrit/server/approval/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/approval/ApprovalsUtil.java
@@ -36,6 +36,7 @@
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
@@ -724,6 +725,7 @@
return filterApprovals(byPatchSet(notes, psId), accountId);
}
+ @Nullable
public PatchSetApproval getSubmitter(ChangeNotes notes, PatchSet.Id c) {
if (c == null) {
return null;
@@ -736,6 +738,7 @@
}
}
+ @Nullable
public static PatchSetApproval getSubmitter(PatchSet.Id c, Iterable<PatchSetApproval> approvals) {
if (c == null) {
return null;
diff --git a/java/com/google/gerrit/server/cache/CacheInfo.java b/java/com/google/gerrit/server/cache/CacheInfo.java
index d6eb065..94a9e05 100644
--- a/java/com/google/gerrit/server/cache/CacheInfo.java
+++ b/java/com/google/gerrit/server/cache/CacheInfo.java
@@ -16,6 +16,7 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
+import com.google.gerrit.common.Nullable;
public class CacheInfo {
@@ -53,6 +54,7 @@
}
}
+ @Nullable
private static String duration(double ns) {
if (ns < 0.5) {
return null;
@@ -118,6 +120,7 @@
disk = percent(value, total);
}
+ @Nullable
private static Integer percent(long value, long total) {
if (total <= 0) {
return null;
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheBaseFactory.java b/java/com/google/gerrit/server/cache/PersistentCacheBaseFactory.java
index ec527ba..e9b254b 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheBaseFactory.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheBaseFactory.java
@@ -18,6 +18,7 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import java.io.IOException;
@@ -80,6 +81,7 @@
return !diskEnabled || diskLimit <= 0;
}
+ @Nullable
private static Path getCacheDir(SitePaths site, String name) {
if (name == null) {
return null;
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
index aa62745..b744058 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
@@ -47,6 +47,7 @@
return source.refreshAfterWrite();
}
+ @Nullable
@Override
public Weigher<K, V> weigher() {
Weigher<K, V> weigher = source.weigher();
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 0403408..8327b88 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -103,6 +103,7 @@
this.mem = mem;
}
+ @Nullable
@Override
public V getIfPresent(Object objKey) {
if (!keyType.getRawType().isInstance(objKey)) {
@@ -423,6 +424,7 @@
return b == null || b.mightContain(key);
}
+ @Nullable
private BloomFilter<K> buildBloomFilter() {
SqlHandle c = null;
try {
@@ -472,6 +474,7 @@
}
}
+ @Nullable
ValueHolder<V> getIfPresent(K key) {
SqlHandle c = null;
try {
@@ -717,6 +720,7 @@
}
}
+ @Nullable
private SqlHandle close(SqlHandle h) {
if (h != null) {
h.close();
@@ -776,6 +780,7 @@
}
}
+ @Nullable
private PreparedStatement closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
diff --git a/java/com/google/gerrit/server/change/ActionJson.java b/java/com/google/gerrit/server/change/ActionJson.java
index 63e2c08..ed6d53d 100644
--- a/java/com/google/gerrit/server/change/ActionJson.java
+++ b/java/com/google/gerrit/server/change/ActionJson.java
@@ -106,6 +106,7 @@
to.actions = toActionMap(rsrc, visitors, changeInfo, copy(visitors, to));
}
+ @Nullable
private ChangeInfo copy(List<ActionVisitor> visitors, ChangeInfo changeInfo) {
if (visitors.isEmpty()) {
return null;
@@ -152,6 +153,7 @@
return copy;
}
+ @Nullable
private RevisionInfo copy(List<ActionVisitor> visitors, RevisionInfo revisionInfo) {
if (visitors.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index a041ff7..d575324 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -30,6 +30,7 @@
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
@@ -409,6 +410,7 @@
return this;
}
+ @Nullable
public String getChangeMessage() {
if (message == null) {
return null;
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 0775647..38efc44 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -764,6 +764,7 @@
return serverIdent.get();
}
+ @Nullable
private RevCommit parseCommit(ObjectId objId, String desc) {
try {
return rw.parseCommit(objId);
diff --git a/java/com/google/gerrit/server/change/FileInfoJsonImpl.java b/java/com/google/gerrit/server/change/FileInfoJsonImpl.java
index 0f9194f..d9c30d7 100644
--- a/java/com/google/gerrit/server/change/FileInfoJsonImpl.java
+++ b/java/com/google/gerrit/server/change/FileInfoJsonImpl.java
@@ -42,6 +42,7 @@
this.diffs = diffOperations;
}
+ @Nullable
@Override
public Map<String, FileInfo> getFileInfoMap(
Change change, ObjectId objectId, @Nullable PatchSet base)
@@ -63,6 +64,7 @@
}
}
+ @Nullable
@Override
public Map<String, FileInfo> getFileInfoMap(
Project.NameKey project, ObjectId objectId, int parent)
diff --git a/java/com/google/gerrit/server/change/LabelsJson.java b/java/com/google/gerrit/server/change/LabelsJson.java
index 69a84dd8..cfa15ae 100644
--- a/java/com/google/gerrit/server/change/LabelsJson.java
+++ b/java/com/google/gerrit/server/change/LabelsJson.java
@@ -80,6 +80,7 @@
* lazily populate accounts. Callers have to call {@link AccountLoader#fill()} afterwards to
* populate all accounts in the returned {@link LabelInfo}s.
*/
+ @Nullable
Map<String, LabelInfo> labelsFor(
AccountLoader accountLoader, ChangeData cd, boolean standard, boolean detailed)
throws PermissionBackendException {
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index 2d36df2..ba938ee 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -17,6 +17,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
@@ -71,6 +72,7 @@
@AutoValue
public abstract static class Base {
+ @Nullable
private static Base create(ChangeNotes notes, PatchSet ps) {
if (notes == null) {
return null;
diff --git a/java/com/google/gerrit/server/config/DownloadConfig.java b/java/com/google/gerrit/server/config/DownloadConfig.java
index 496808a..d581675 100644
--- a/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.CoreDownloadSchemes;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
import com.google.gerrit.server.change.ArchiveFormatInternal;
@@ -87,6 +88,7 @@
return list.size() == 1 && list.get(0) == null;
}
+ @Nullable
private static String toCoreScheme(String s) {
try {
Field f = CoreDownloadSchemes.class.getField(s.toUpperCase());
diff --git a/java/com/google/gerrit/server/config/GitwebConfig.java b/java/com/google/gerrit/server/config/GitwebConfig.java
index c477bb5..028e172 100644
--- a/java/com/google/gerrit/server/config/GitwebConfig.java
+++ b/java/com/google/gerrit/server/config/GitwebConfig.java
@@ -99,6 +99,7 @@
return values.length > 0 && isNullOrEmpty(values[0]);
}
+ @Nullable
private static GitwebType typeFromConfig(Config cfg) {
GitwebType defaultType = defaultType(cfg.getString("gitweb", null, "type"));
if (defaultType == null) {
@@ -136,6 +137,7 @@
return type;
}
+ @Nullable
private static GitwebType defaultType(String typeName) {
GitwebType type = new GitwebType();
switch (nullToEmpty(typeName)) {
@@ -283,6 +285,7 @@
this.tag = parse(type.getTag());
}
+ @Nullable
@Override
public WebLinkInfo getBranchWebLink(String projectName, String branchName) {
if (branch != null) {
@@ -295,6 +298,7 @@
return null;
}
+ @Nullable
@Override
public WebLinkInfo getTagWebLink(String projectName, String tagName) {
if (tag != null) {
@@ -304,6 +308,7 @@
return null;
}
+ @Nullable
@Override
public WebLinkInfo getFileHistoryWebLink(String projectName, String revision, String fileName) {
if (fileHistory != null) {
@@ -317,6 +322,7 @@
return null;
}
+ @Nullable
@Override
public WebLinkInfo getFileWebLink(
String projectName, String revision, String hash, String fileName) {
@@ -331,6 +337,7 @@
return null;
}
+ @Nullable
@Override
public WebLinkInfo getPatchSetWebLink(
String projectName, String commit, String commitMessage, String branchName) {
@@ -359,6 +366,7 @@
return getPatchSetWebLink(projectName, commit, commitMessage, branchName);
}
+ @Nullable
@Override
public WebLinkInfo getProjectWeblink(String projectName) {
if (project != null) {
@@ -378,6 +386,7 @@
return new WebLinkInfo(type.getLinkName(), null, url + rest, null);
}
+ @Nullable
private static ParameterizedString parse(String pattern) {
if (!isNullOrEmpty(pattern)) {
return new ParameterizedString(pattern);
diff --git a/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index c09988e3..e11d6aa 100644
--- a/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -17,6 +17,7 @@
import static java.util.stream.Collectors.toList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
@@ -360,6 +361,7 @@
}
}
+ @Nullable
private ProjectConfig parseConfig(Project.NameKey p, String idStr)
throws IOException, ConfigInvalidException, RepositoryNotFoundException {
ObjectId id = ObjectId.fromString(idStr);
@@ -382,14 +384,17 @@
}
}
+ @Nullable
private static Boolean toBoolean(String value) {
return value != null ? Boolean.parseBoolean(value) : null;
}
+ @Nullable
private static Integer toInt(String value) {
return value != null ? Integer.parseInt(value) : null;
}
+ @Nullable
private static Long toLong(String value) {
return value != null ? Long.parseLong(value) : null;
}
diff --git a/java/com/google/gerrit/server/config/RepositoryConfig.java b/java/com/google/gerrit/server/config/RepositoryConfig.java
index f722321..d569c87 100644
--- a/java/com/google/gerrit/server/config/RepositoryConfig.java
+++ b/java/com/google/gerrit/server/config/RepositoryConfig.java
@@ -55,6 +55,7 @@
cfg.getStringList(SECTION_NAME, findSubSection(project.get()), OWNER_GROUP_NAME));
}
+ @Nullable
public Path getBasePath(Project.NameKey project) {
String basePath = cfg.getString(SECTION_NAME, findSubSection(project.get()), BASE_PATH_NAME);
return basePath != null ? Paths.get(basePath) : null;
diff --git a/java/com/google/gerrit/server/config/SitePaths.java b/java/com/google/gerrit/server/config/SitePaths.java
index 5e268da..2cd24ba 100644
--- a/java/com/google/gerrit/server/config/SitePaths.java
+++ b/java/com/google/gerrit/server/config/SitePaths.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.config;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -140,6 +141,7 @@
* @param path the path string to resolve. May be null.
* @return the resolved path; null if {@code path} was null or empty.
*/
+ @Nullable
public Path resolve(String path) {
if (path != null && !path.isEmpty()) {
Path loc = site_path.resolve(path).normalize();
diff --git a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index c2c0b05..0e911b9 100644
--- a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -21,6 +21,7 @@
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
import com.vladsch.flexmark.html.HtmlRenderer;
@@ -126,6 +127,7 @@
return findTitle(parseMarkdown(md));
}
+ @Nullable
private String findTitle(Node root) {
if (root instanceof Heading) {
Heading h = (Heading) root;
diff --git a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index 7d3ddf1..cd49ea6 100644
--- a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -99,6 +100,7 @@
}
}
+ @Nullable
protected Directory readIndexDirectory() throws IOException {
Directory dir = new ByteBuffersDirectory();
byte[] buffer = new byte[4096];
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 95f6d96..60e30bc 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
@@ -523,6 +524,7 @@
}
/** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
+ @Nullable
public AccountAttribute asAccountAttribute(Account.Id id) {
if (id == null) {
return null;
@@ -590,6 +592,7 @@
}
/** Get a link to the change; null if the server doesn't know its own address. */
+ @Nullable
private String getChangeUrl(Change change) {
if (change != null) {
return urlFormatter.get().getChangeViewUrl(change.getProject(), change.getId()).orElse(null);
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index afe2a7c..18f3d7a 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -20,6 +20,7 @@
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
@@ -234,6 +235,7 @@
});
}
+ @Nullable
String[] hashtagArray(Collection<String> hashtags) {
if (hashtags != null && !hashtags.isEmpty()) {
return Sets.newHashSet(hashtags).toArray(new String[hashtags.size()]);
diff --git a/java/com/google/gerrit/server/extensions/events/EventUtil.java b/java/com/google/gerrit/server/extensions/events/EventUtil.java
index b669571..7c8777f 100644
--- a/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -99,6 +99,7 @@
return revisionJsonFactory.create(changeOptions).getRevisionInfo(cd, ps);
}
+ @Nullable
public AccountInfo accountInfo(@Nullable AccountState accountState) {
if (accountState == null || accountState.account().id() == null) {
return null;
diff --git a/java/com/google/gerrit/server/git/GroupCollector.java b/java/com/google/gerrit/server/git/GroupCollector.java
index 5bbe5e2..455b221 100644
--- a/java/com/google/gerrit/server/git/GroupCollector.java
+++ b/java/com/google/gerrit/server/git/GroupCollector.java
@@ -27,6 +27,7 @@
import com.google.common.collect.Sets;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.PatchSetUtil;
@@ -258,6 +259,7 @@
return actual;
}
+ @Nullable
private ObjectId parseGroup(ObjectId forCommit, String group) {
try {
return ObjectId.fromString(group);
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index ae247ad..6922efb 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -1027,6 +1027,7 @@
}
}
+ @Nullable
public static CodeReviewCommit findAnyMergedInto(
CodeReviewRevWalk rw, Iterable<CodeReviewCommit> commits, CodeReviewCommit tip)
throws IOException {
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index f66a089..fb34753 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -82,6 +82,7 @@
throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
}
+ @Nullable
@Override
public Ref exactRef(String name) throws IOException {
Ref ref = getDelegate().getRefDatabase().exactRef(name);
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index f516a0c..e8b7c62 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -18,6 +18,7 @@
import com.google.common.base.CaseFormat;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -241,6 +242,7 @@
}
/** Locate a task by its unique id, null if no task matches. */
+ @Nullable
public Task<?> getTask(int id) {
Task<?> result = null;
for (Executor e : queues) {
@@ -256,6 +258,7 @@
return result;
}
+ @Nullable
public ScheduledThreadPoolExecutor getExecutor(String queueName) {
for (Executor e : queues) {
if (e.queueName.equals(queueName)) {
diff --git a/java/com/google/gerrit/server/git/meta/TabFile.java b/java/com/google/gerrit/server/git/meta/TabFile.java
index 80570a5..5f76b39 100644
--- a/java/com/google/gerrit/server/git/meta/TabFile.java
+++ b/java/com/google/gerrit/server/git/meta/TabFile.java
@@ -17,6 +17,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.git.ValidationError;
import java.io.BufferedReader;
import java.io.IOException;
@@ -84,6 +85,7 @@
return map;
}
+ @Nullable
protected static String asText(String left, String right, Map<String, String> entries) {
if (entries.isEmpty()) {
return null;
@@ -96,6 +98,7 @@
return asText(left, right, rows);
}
+ @Nullable
protected static String asText(String left, String right, List<Row> rows) {
if (rows.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index ae7c3a10..6e5cfff 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -441,6 +441,7 @@
update, message.toString(), ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
}
+ @Nullable
private String changeKindMessage(ChangeKind changeKind) {
switch (changeKind) {
case MERGE_FIRST_PARENT_UPDATE:
@@ -624,6 +625,7 @@
return cmd;
}
+ @Nullable
private static String findMergedInto(Context ctx, String first, RevCommit commit) {
try {
RevWalk rw = ctx.getRevWalk();
diff --git a/java/com/google/gerrit/server/group/GroupResolver.java b/java/com/google/gerrit/server/group/GroupResolver.java
index 546614c..001a153 100644
--- a/java/com/google/gerrit/server/group/GroupResolver.java
+++ b/java/com/google/gerrit/server/group/GroupResolver.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.group;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
@@ -84,6 +85,7 @@
* @param id ID of the group, can be a group UUID, a group name or a legacy group ID
* @return the group, null if no group is found for the given group ID
*/
+ @Nullable
public GroupDescription.Basic parseId(String id) {
logger.atFine().log("Parsing group %s", id);
diff --git a/java/com/google/gerrit/server/group/SystemGroupBackend.java b/java/com/google/gerrit/server/group/SystemGroupBackend.java
index 5a9b9e5..0471acc 100644
--- a/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -21,6 +21,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
@@ -140,6 +141,7 @@
return isSystemGroup(uuid);
}
+ @Nullable
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
final GroupReference ref = uuids.get(uuid);
@@ -157,11 +159,13 @@
return ref.getUUID();
}
+ @Nullable
@Override
public String getUrl() {
return null;
}
+ @Nullable
@Override
public String getEmailAddress() {
return null;
diff --git a/java/com/google/gerrit/server/group/testing/BUILD b/java/com/google/gerrit/server/group/testing/BUILD
index 77bb777..bb2b20d 100644
--- a/java/com/google/gerrit/server/group/testing/BUILD
+++ b/java/com/google/gerrit/server/group/testing/BUILD
@@ -7,6 +7,7 @@
testonly = True,
srcs = glob(["*.java"]),
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
"//lib:guava",
diff --git a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
index 12d8c93..d81992a 100644
--- a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
+++ b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
@@ -111,6 +112,7 @@
return false;
}
+ @Nullable
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
return uuid == null ? null : groups.get(uuid);
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index a39cbe5..03f4c50 100644
--- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -26,6 +26,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.index.SiteIndexer;
@@ -179,6 +180,7 @@
return Result.create(sw, ok.get(), nDone, nFailed);
}
+ @Nullable
public Callable<Void> reindexProject(
ChangeIndexer indexer, Project.NameKey project, Task done, Task failed) {
try (Repository repo = repoManager.openRepository(project)) {
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 670f472a..68d3a3e 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -1393,6 +1393,7 @@
},
(cd, field) -> cd.setRefStatePatterns(field));
+ @Nullable
private static String getTopic(ChangeData cd) {
Change c = cd.change();
if (c == null) {
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index a1756bf..973d691 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -184,6 +184,7 @@
* @throws QueryParseException if the underlying index implementation does not support this
* predicate.
*/
+ @Nullable
private Predicate<ChangeData> rewriteImpl(
Predicate<ChangeData> in, ChangeIndex index, QueryOptions opts, MutableInteger leafTerms)
throws QueryParseException {
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 6849831..4b88919 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -407,6 +407,7 @@
return future;
}
+ @Nullable
@Override
public ChangeData callImpl() throws Exception {
// Remove this task from queuedIndexTasks. This is done right at the beginning of this task so
@@ -460,6 +461,7 @@
this.id = id;
}
+ @Nullable
@Override
public ChangeData call() {
logger.atFine().log("Delete change %d from index.", id.get());
diff --git a/java/com/google/gerrit/server/ioutil/BUILD b/java/com/google/gerrit/server/ioutil/BUILD
index fd0c4f1..6b9ecdf 100644
--- a/java/com/google/gerrit/server/ioutil/BUILD
+++ b/java/com/google/gerrit/server/ioutil/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//lib:automaton",
"//lib:guava",
diff --git a/java/com/google/gerrit/server/ioutil/BasicSerialization.java b/java/com/google/gerrit/server/ioutil/BasicSerialization.java
index 296cf22..b43655a 100644
--- a/java/com/google/gerrit/server/ioutil/BasicSerialization.java
+++ b/java/com/google/gerrit/server/ioutil/BasicSerialization.java
@@ -31,6 +31,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.CodedEnum;
import java.io.EOFException;
import java.io.IOException;
@@ -129,6 +130,7 @@
}
/** Read a UTF-8 string, prefixed by its byte length in a varint. */
+ @Nullable
public static String readString(InputStream input) throws IOException {
final byte[] bin = readBytes(input);
if (bin.length == 0) {
diff --git a/java/com/google/gerrit/server/mail/send/AddKeySender.java b/java/com/google/gerrit/server/mail/send/AddKeySender.java
index 652766a..6fe3cbe 100644
--- a/java/com/google/gerrit/server/mail/send/AddKeySender.java
+++ b/java/com/google/gerrit/server/mail/send/AddKeySender.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail.send;
import com.google.common.base.Joiner;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.server.IdentifiedUser;
@@ -111,10 +112,12 @@
return "Unknown";
}
+ @Nullable
private String getSshKey() {
return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
}
+ @Nullable
private String getGpgKeys() {
if (gpgKeys != null) {
return Joiner.on("\n").join(gpgKeys);
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index 3c821cc..79696fe 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -87,16 +87,19 @@
public List<Comment> comments = new ArrayList<>();
/** Returns a web link to a comment for a change. */
+ @Nullable
public String getCommentLink(String uuid) {
return args.urlFormatter.get().getInlineCommentView(change, uuid).orElse(null);
}
/** Returns a web link to the comment tab view of a change. */
+ @Nullable
public String getCommentsTabLink() {
return args.urlFormatter.get().getCommentsTabView(change).orElse(null);
}
/** Returns a web link to the findings tab view of a change. */
+ @Nullable
public String getFindingsTabLink() {
return args.urlFormatter.get().getFindingsTabView(change).orElse(null);
}
@@ -505,6 +508,7 @@
return false;
}
+ @Nullable
private Repository getRepository() {
try {
return args.server.openRepository(projectState.getNameKey());
diff --git a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java b/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
index d6d306c..64a01ff 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail.send;
import com.google.common.base.Joiner;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.server.IdentifiedUser;
@@ -109,10 +110,12 @@
throw new IllegalStateException("key type is not SSH or GPG");
}
+ @Nullable
private String getSshKey() {
return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
}
+ @Nullable
private String getGpgKeyFingerprints() {
if (!gpgKeyFingerprints.isEmpty()) {
return Joiner.on("\n").join(gpgKeyFingerprints);
diff --git a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
index 70676e3..bd79d3a 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.Change;
@@ -73,6 +74,7 @@
}
}
+ @Nullable
public List<String> getReviewerNames() {
if (reviewers.isEmpty() && reviewersByEmail.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/mail/send/NewChangeSender.java b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
index e899fc5..800066e 100644
--- a/java/com/google/gerrit/server/mail/send/NewChangeSender.java
+++ b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.exceptions.EmailException;
@@ -96,6 +97,7 @@
}
}
+ @Nullable
public List<String> getReviewerNames() {
if (reviewers.isEmpty()) {
return null;
@@ -107,6 +109,7 @@
return names;
}
+ @Nullable
public List<String> getRemovedReviewerNames() {
if (removedReviewers.isEmpty() && removedByEmailReviewers.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/mail/send/NotificationEmail.java b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index 5b209ce..f023075 100644
--- a/java/com/google/gerrit/server/mail/send/NotificationEmail.java
+++ b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -17,6 +17,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.BranchNameKey;
@@ -92,6 +93,7 @@
protected abstract void addWatcher(RecipientType type, Account.Id to);
+ @Nullable
public String getSshHost() {
String host = Iterables.getFirst(args.sshAddresses, null);
if (host == null) {
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index bfc1f5b..55f82d4 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -380,10 +380,12 @@
return SystemReader.getInstance().getHostname();
}
+ @Nullable
public String getSettingsUrl() {
return args.urlFormatter.get().getSettingsUrl().orElse(null);
}
+ @Nullable
private String getGerritUrl() {
return args.urlFormatter.get().getWebUrl().orElse(null);
}
@@ -471,6 +473,7 @@
* @param accountId user to fetch.
* @return name/email of account, username, or null if unset or the accountId is null.
*/
+ @Nullable
protected String getUserNameEmailFor(@Nullable Account.Id accountId) {
if (accountId == null) {
return null;
@@ -594,6 +597,7 @@
}
}
+ @Nullable
private Address toAddress(Account.Id id) {
Optional<Account> accountState = args.accountCache.get(id).map(AccountState::account);
if (!accountState.isPresent()) {
diff --git a/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java b/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
index 0d32dd5..5f31c68 100644
--- a/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
+++ b/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
@@ -142,6 +142,7 @@
}
}
+ @Nullable
public ImmutableList<String> getReviewerNames() {
List<String> names = new ArrayList<>();
for (Account.Id id : reviewers) {
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 158972f..e76af3f 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -199,6 +199,7 @@
return load();
}
+ @Nullable
public ObjectId loadRevision() {
if (loaded) {
return getRevision();
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index e6f1622..ba91c68 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -101,6 +101,7 @@
user);
}
+ @Nullable
private static Account.Id accountId(CurrentUser u) {
checkUserType(u);
return (u instanceof IdentifiedUser) ? u.getAccountId() : null;
@@ -206,6 +207,7 @@
* deleted.
* @throws IOException if a lower-level error occurred.
*/
+ @Nullable
final ObjectId apply(RevWalk rw, ObjectInserter ins, ObjectId curr) throws IOException {
if (isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 73161d7..0dcf786 100644
--- a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -19,6 +19,7 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Comment;
@@ -187,6 +188,7 @@
return clonedUpdate;
}
+ @Nullable
private CommitBuilder storeCommentsInNotes(
RevWalk rw, ObjectInserter ins, ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, IOException {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 26d5933..9334ad3 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -673,6 +673,7 @@
return change.getProject();
}
+ @Nullable
@Override
protected ObjectId readRef(Repository repo) throws IOException {
return refs != null ? refs.get(getRefName()).orElse(null) : super.readRef(repo);
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 6e5e19c..e8e2f17 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -323,6 +323,7 @@
return result;
}
+ @Nullable
private PatchSet.Id buildCurrentPatchSetId() {
// currentPatchSets are in parse order, i.e. newest first. Pick the first
// patch set that was marked as current, excluding deleted patch sets.
@@ -576,6 +577,7 @@
return parseOneFooter(commit, FOOTER_SUBMISSION_ID);
}
+ @Nullable
private String parseBranch(ChangeNotesCommit commit) throws ConfigInvalidException {
String branch = parseOneFooter(commit, FOOTER_BRANCH);
return branch != null ? RefNames.fullName(branch) : null;
@@ -603,6 +605,7 @@
return parseOneFooter(commit, FOOTER_TOPIC);
}
+ @Nullable
private String parseOneFooter(ChangeNotesCommit commit, FooterKey footerKey)
throws ConfigInvalidException {
List<String> footerLines = commit.getFooterLineValues(footerKey);
@@ -623,6 +626,7 @@
return line;
}
+ @Nullable
private ObjectId parseRevision(ChangeNotesCommit commit) throws ConfigInvalidException {
String sha = parseOneFooter(commit, FOOTER_COMMIT);
if (sha == null) {
@@ -760,6 +764,7 @@
}
}
+ @Nullable
private Change.Status parseStatus(ChangeNotesCommit commit) throws ConfigInvalidException {
List<String> statusLines = commit.getFooterLineValues(FOOTER_STATUS);
if (statusLines.isEmpty()) {
@@ -798,6 +803,7 @@
return PatchSet.id(id, psId);
}
+ @Nullable
private PatchSetState parsePatchSetState(ChangeNotesCommit commit) throws ConfigInvalidException {
String psIdLine = parseExactlyOneFooter(commit, FOOTER_PATCH_SET);
int s = psIdLine.indexOf(' ');
@@ -1138,6 +1144,7 @@
}
}
+ @Nullable
private Account.Id parseIdent(ChangeNotesCommit commit) throws ConfigInvalidException {
// Check if the author name/email is the same as the committer name/email,
// i.e. was the server ident at the time this commit was made.
@@ -1223,6 +1230,7 @@
throw invalidFooter(FOOTER_WORK_IN_PROGRESS, raw);
}
+ @Nullable
private Change.Id parseRevertOf(ChangeNotesCommit commit) throws ConfigInvalidException {
String footer = parseOneFooter(commit, FOOTER_REVERT_OF);
if (footer == null) {
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 62c734b..1cd0fb0 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -571,6 +571,7 @@
}
/** Returns the tree id for the updated tree */
+ @Nullable
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
throws ConfigInvalidException, IOException {
if (submitRequirementResults == null && comments.isEmpty() && pushCert == null) {
diff --git a/java/com/google/gerrit/server/notedb/CommitRewriter.java b/java/com/google/gerrit/server/notedb/CommitRewriter.java
index da20475..65c83c8 100644
--- a/java/com/google/gerrit/server/notedb/CommitRewriter.java
+++ b/java/com/google/gerrit/server/notedb/CommitRewriter.java
@@ -893,7 +893,7 @@
commitMessageRange.get().subjectEnd());
Optional<String> fixedChangeMessage = Optional.empty();
String originalChangeMessage = null;
- if (commitMessageRange.isPresent() && commitMessageRange.get().hasChangeMessage()) {
+ if (commitMessageRange.get().hasChangeMessage()) {
originalChangeMessage =
RawParseUtils.decode(
enc,
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 5d8f57f..bdfe378 100644
--- a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -141,6 +141,7 @@
return args.allUsers;
}
+ @Nullable
@VisibleForTesting
NoteMap getNoteMap() {
return revisionNoteMap != null ? revisionNoteMap.noteMap : null;
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index ad1f4c5..0939ada 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -365,6 +365,7 @@
cu -> cu.getAttentionSetUpdates().stream()));
}
+ @Nullable
private BatchRefUpdate execute(OpenRepo or, boolean dryrun, @Nullable PushCertificate pushCert)
throws IOException {
if (or == null || or.cmds.isEmpty()) {
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUtil.java b/java/com/google/gerrit/server/notedb/NoteDbUtil.java
index 396e29b..7f10596 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUtil.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUtil.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.RestModifyView;
import java.sql.Timestamp;
@@ -68,6 +69,7 @@
* Returns the name of the REST API handler that is in the stack trace of the caller of this
* method.
*/
+ @Nullable
static String guessRestApiHandler() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
int i = findRestApiServlet(trace);
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java b/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
index 7f067f5..e1e6305 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
@@ -19,6 +19,7 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
@@ -100,6 +101,7 @@
put.add(c);
}
+ @Nullable
private CommitBuilder storeCommentsInNotes(
RevWalk rw, ObjectInserter ins, ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, IOException {
diff --git a/java/com/google/gerrit/server/patch/BaseCommitUtil.java b/java/com/google/gerrit/server/patch/BaseCommitUtil.java
index 56a01b9..9a103cd 100644
--- a/java/com/google/gerrit/server/patch/BaseCommitUtil.java
+++ b/java/com/google/gerrit/server/patch/BaseCommitUtil.java
@@ -91,6 +91,7 @@
* @return Returns the parent commit of the commit represented by the commitId parameter. Note
* that auto-merge is not supported for commits having more than two parents.
*/
+ @Nullable
RevObject getParentCommit(
Repository repo,
ObjectInserter ins,
diff --git a/java/com/google/gerrit/server/patch/FilePathAdapter.java b/java/com/google/gerrit/server/patch/FilePathAdapter.java
index 2c98f1a..d0b7ac6 100644
--- a/java/com/google/gerrit/server/patch/FilePathAdapter.java
+++ b/java/com/google/gerrit/server/patch/FilePathAdapter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.patch;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Patch.ChangeType;
import java.util.Optional;
@@ -30,6 +31,7 @@
/**
* Converts the old file path of the new diff cache output to the old diff cache representation.
*/
+ @Nullable
public static String getOldPath(Optional<String> oldName, ChangeType changeType) {
switch (changeType) {
case DELETED:
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 33300e3..1612925 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.entities.FixReplacement;
@@ -209,6 +210,7 @@
}
}
+ @Nullable
private static String oldName(PatchFileChange entry) {
switch (entry.getChangeType()) {
case ADDED:
@@ -224,6 +226,7 @@
}
}
+ @Nullable
private static String newName(PatchFileChange entry) {
switch (entry.getChangeType()) {
case DELETED:
@@ -412,6 +415,7 @@
treeId, path, id, mode, srcContent, src, mimeType, displayMethod, fileMode);
}
+ @Nullable
private TreeWalk find(ObjectReader reader, String path, ObjectId within) throws IOException {
if (path == null || within == null) {
return null;
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index e4fa1c4..c235012 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -21,6 +21,7 @@
import static com.google.gerrit.server.util.MagicBranch.NEW_CHANGE;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.BranchNameKey;
@@ -269,6 +270,7 @@
return false;
}
+ @Nullable
private Boolean canPerform(String permissionName, AccessSection section, Permission permission) {
for (PermissionRule rule : permission.getRules()) {
if (rule.isBlock() || rule.isDeny() || !match(rule)) {
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 478ba5c..ba292e6 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.PermissionRange;
@@ -177,6 +178,7 @@
}
/** The range of permitted values associated with a label permission. */
+ @Nullable
PermissionRange getRange(String permission, boolean isChangeOwner) {
if (Permission.hasRange(permission)) {
return toRange(permission, isChangeOwner);
diff --git a/java/com/google/gerrit/server/plugins/JarScanner.java b/java/com/google/gerrit/server/plugins/JarScanner.java
index e119bf1..122e3f4 100644
--- a/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -24,6 +24,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
@@ -212,6 +213,7 @@
this.superName = superName;
}
+ @Nullable
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (!visible) {
diff --git a/java/com/google/gerrit/server/plugins/PluginLoader.java b/java/com/google/gerrit/server/plugins/PluginLoader.java
index 8d17d85..3263636 100644
--- a/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -27,6 +27,7 @@
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
@@ -713,6 +714,7 @@
return Iterables.filter(paths, p -> !p.getFileName().toString().endsWith(".disabled"));
}
+ @Nullable
public String getGerritPluginName(Path srcPath) {
String fileName = srcPath.getFileName().toString();
if (isUiPlugin(fileName)) {
diff --git a/java/com/google/gerrit/server/plugins/ServerPlugin.java b/java/com/google/gerrit/server/plugins/ServerPlugin.java
index 320b618..af948b0 100644
--- a/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -110,6 +110,7 @@
}
}
+ @Nullable
@SuppressWarnings("unchecked")
protected static Class<? extends Module> load(@Nullable String name, ClassLoader pluginLoader)
throws ClassNotFoundException {
diff --git a/java/com/google/gerrit/server/project/CreateProjectArgs.java b/java/com/google/gerrit/server/project/CreateProjectArgs.java
index c1b7b86..196873f 100644
--- a/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.InheritableBoolean;
@@ -56,6 +57,7 @@
return projectName;
}
+ @Nullable
public String getProjectName() {
return projectName != null ? projectName.get() : null;
}
diff --git a/java/com/google/gerrit/server/project/GroupList.java b/java/com/google/gerrit/server/project/GroupList.java
index 98dc44a..1b0ba97 100644
--- a/java/com/google/gerrit/server/project/GroupList.java
+++ b/java/com/google/gerrit/server/project/GroupList.java
@@ -126,6 +126,7 @@
byUUID.put(uuid, reference);
}
+ @Nullable
public String asText() {
if (byUUID.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/project/LabelDefinitionJson.java b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
index 235eb34..f46c2b1 100644
--- a/java/com/google/gerrit/server/project/LabelDefinitionJson.java
+++ b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
@@ -16,6 +16,7 @@
import static java.util.stream.Collectors.toMap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.LabelValue;
import com.google.gerrit.entities.Project;
@@ -39,8 +40,9 @@
return label;
}
+ @Nullable
private static Boolean toBoolean(boolean v) {
- return v ? v : null;
+ return v ? Boolean.TRUE : null;
}
private LabelDefinitionJson() {}
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 654b3a4..2dd7970 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -1203,6 +1203,7 @@
return false;
}
+ @Nullable
private List<String> getStringListOrNull(
Config rc, String section, String subSection, String name) {
String[] ac = rc.getStringList(section, subSection, name);
@@ -1372,6 +1373,7 @@
return true;
}
+ @Nullable
public static String validMaxObjectSizeLimit(String value) throws ConfigInvalidException {
if (value == null) {
return null;
diff --git a/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java b/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
index ccb5651..929399a 100644
--- a/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
+++ b/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
@@ -18,6 +18,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import java.util.Iterator;
@@ -63,6 +64,7 @@
return n;
}
+ @Nullable
private ProjectState computeNext(ProjectState n) {
Project.NameKey parentName = n.getProject().getParent();
if (parentName != null && visit(parentName)) {
diff --git a/java/com/google/gerrit/server/project/SectionMatcher.java b/java/com/google/gerrit/server/project/SectionMatcher.java
index 3d7175f..eaebab2 100644
--- a/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
@@ -25,6 +26,7 @@
* of which sections are relevant to any given input reference.
*/
public class SectionMatcher extends RefPatternMatcher {
+ @Nullable
static SectionMatcher wrap(Project.NameKey project, AccessSection section) {
String ref = section.getName();
if (AccessSection.isValidRefSectionName(ref)) {
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index ed950c8..203e318 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -17,6 +17,7 @@
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.NotSignedInException;
import com.google.gerrit.exceptions.StorageException;
@@ -98,6 +99,7 @@
}
}
+ @Nullable
Schema<AccountState> schema() {
Index<?, AccountState> index = indexes != null ? indexes.getSearchIndex() : null;
return index != null ? index.getSchema() : null;
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 98a12d5..fa1758a 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Project;
import com.google.gerrit.index.IndexConfig;
@@ -71,6 +72,7 @@
return query(AccountPredicates.externalIdIncludingSecondaryEmails(externalId.toString()));
}
+ @Nullable
@UsedAt(UsedAt.Project.COLLABNET)
public AccountState oneByExternalId(ExternalId.Key externalId) {
List<AccountState> accountStates = byExternalId(externalId);
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 1349523..ec1fcad 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -580,6 +580,7 @@
return notes;
}
+ @Nullable
public PatchSet currentPatchSet() {
if (currentPatchSet == null) {
Change c = change();
@@ -624,6 +625,7 @@
currentApprovals = approvals;
}
+ @Nullable
public String commitMessage() {
if (commitMessage == null) {
if (!loadCommitData()) {
@@ -647,6 +649,7 @@
return trackingFooters.extract(commitFooters());
}
+ @Nullable
public PersonIdent getAuthor() {
if (author == null) {
if (!loadCommitData()) {
@@ -656,6 +659,7 @@
return author;
}
+ @Nullable
public PersonIdent getCommitter() {
if (committer == null) {
if (!loadCommitData()) {
@@ -751,6 +755,7 @@
}
/** Returns patch with the given ID, or null if it does not exist. */
+ @Nullable
public PatchSet patchSet(PatchSet.Id psId) {
if (currentPatchSet != null && currentPatchSet.id().equals(psId)) {
return currentPatchSet;
@@ -892,6 +897,7 @@
return robotComments;
}
+ @Nullable
public Integer unresolvedCommentCount() {
if (unresolvedCommentCount == null) {
if (!lazyload()) {
@@ -914,6 +920,7 @@
this.unresolvedCommentCount = count;
}
+ @Nullable
public Integer totalCommentCount() {
if (totalCommentCount == null) {
if (!lazyload()) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 91ec74c..3c69fbf 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -28,6 +28,7 @@
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Address;
@@ -473,6 +474,7 @@
}
}
+ @Nullable
Schema<ChangeData> getSchema() {
return index != null ? index.getSchema() : null;
}
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index 6aacfc9..5e8674e 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -119,6 +119,7 @@
return count == null ? matchingVotes >= 1 : matchingVotes == count;
}
+ @Nullable
protected static LabelType type(LabelTypes types, String toFind) {
if (types.byLabel(toFind).isPresent()) {
return types.byLabel(toFind).get();
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
index 5a81ca1..e890066 100644
--- a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
@@ -99,6 +99,7 @@
return new EqualsLabelPredicate(args, label, value, account, count);
}
+ @Nullable
protected static LabelType type(LabelTypes types, String toFind) {
if (types.byLabel(toFind).isPresent()) {
return types.byLabel(toFind).get();
diff --git a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
index a3c48b9..d7a5da11 100644
--- a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
+++ b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -92,7 +93,8 @@
return Response.ok(result);
}
+ @Nullable
private static Boolean toBoolean(boolean v) {
- return v ? v : null;
+ return v ? Boolean.TRUE : null;
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
index 8d65aac..d8ad3cf 100644
--- a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
@@ -19,6 +19,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
@@ -93,6 +94,7 @@
return pwi;
}
+ @Nullable
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index 81b6fb3..8ebe71f 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -263,6 +263,7 @@
return rci;
}
+ @Nullable
private List<FixSuggestionInfo> toFixSuggestionInfos(
@Nullable List<FixSuggestion> fixSuggestions) {
if (fixSuggestions == null || fixSuggestions.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
index d818210..66171c4 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -97,6 +98,7 @@
return true;
}
+ @Nullable
public Account.Id getDeletedAssignee() {
return deletedAssignee != null ? deletedAssignee.account().id() : null;
}
diff --git a/java/com/google/gerrit/server/restapi/change/Files.java b/java/com/google/gerrit/server/restapi/change/Files.java
index e996169..7699873 100644
--- a/java/com/google/gerrit/server/restapi/change/Files.java
+++ b/java/com/google/gerrit/server/restapi/change/Files.java
@@ -370,6 +370,7 @@
: Iterables.getFirst(fileDiffList.values(), null).oldCommitId();
}
+ @Nullable
private ObjectId getNewId(Map<String, FileDiffOutput> fileDiffList) {
return fileDiffList.isEmpty()
? null
diff --git a/java/com/google/gerrit/server/restapi/change/GetChange.java b/java/com/google/gerrit/server/restapi/change/GetChange.java
index a81171a..d126d8a 100644
--- a/java/com/google/gerrit/server/restapi/change/GetChange.java
+++ b/java/com/google/gerrit/server/restapi/change/GetChange.java
@@ -144,6 +144,7 @@
cds, this, Streams.stream(pdiFactories.entries()));
}
+ @Nullable
private ObjectId verifyMetaId(Change change, @Nullable ObjectId id) throws RestApiException {
if (id == null) {
return null;
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 560f4e0..5fc4f41 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -234,6 +234,7 @@
* @param user the user who is checking to submit
* @return a reason why any of the changes is not submittable or null
*/
+ @Nullable
private String problemsForSubmittingChangeset(ChangeData cd, ChangeSet cs, CurrentUser user) {
try {
if (cs.furtherHiddenChanges()) {
@@ -297,6 +298,7 @@
return null;
}
+ @Nullable
@Override
public UiAction.Description getDescription(RevisionResource resource)
throws IOException, PermissionBackendException {
@@ -372,6 +374,7 @@
.setEnabled(Boolean.TRUE.equals(enabled));
}
+ @Nullable
public Collection<ChangeData> unmergeableChanges(ChangeSet cs) throws IOException {
Set<ChangeData> mergeabilityMap = new HashSet<>();
Set<ObjectId> outDatedPatchsets = new HashSet<>();
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 09052a6..7604a8f 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -19,6 +19,7 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.ContributorAgreement;
import com.google.gerrit.extensions.common.AccountDefaultDisplayName;
import com.google.gerrit.extensions.common.AccountsInfo;
@@ -303,6 +304,7 @@
return info;
}
+ @Nullable
private String getDocUrl() {
String docUrl = config.getString("gerrit", null, "docUrl");
if (Strings.isNullOrEmpty(docUrl)) {
@@ -328,6 +330,7 @@
private static final String DEFAULT_THEME = "/static/" + SitePaths.THEME_FILENAME;
private static final String DEFAULT_THEME_JS = "/static/" + SitePaths.THEME_JS_FILENAME;
+ @Nullable
private String getDefaultTheme() {
if (config.getString("theme", null, "enableDefault") == null) {
// If not explicitly enabled or disabled, check for the existence of the theme file.
@@ -344,6 +347,7 @@
return null;
}
+ @Nullable
private SshdInfo getSshdInfo() {
String[] addr = config.getStringList("sshd", null, "listenAddress");
if (addr.length == 1 && isOff(addr[0])) {
@@ -380,7 +384,8 @@
return Arrays.asList(config.getStringList("dashboard", null, "submitRequirementColumns"));
}
+ @Nullable
private static Boolean toBoolean(boolean v) {
- return v ? v : null;
+ return v ? Boolean.TRUE : null;
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetSummary.java b/java/com/google/gerrit/server/restapi/config/GetSummary.java
index 23b7a80..34cf550 100644
--- a/java/com/google/gerrit/server/restapi/config/GetSummary.java
+++ b/java/com/google/gerrit/server/restapi/config/GetSummary.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.config;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.Response;
@@ -221,6 +222,7 @@
return jvmSummary;
}
+ @Nullable
private static Integer toInteger(int i) {
return i != 0 ? i : null;
}
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index e617931..9d36aaa 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -17,6 +17,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -181,6 +182,7 @@
return Response.created(json.format(new InternalGroupDescription(createGroup(args))));
}
+ @Nullable
private AccountGroup.UUID owner(GroupInput input) throws UnprocessableEntityException {
if (input.ownerId != null) {
GroupDescription.Internal d = groups.parseInternal(Url.decode(input.ownerId));
diff --git a/java/com/google/gerrit/server/restapi/group/ListGroups.java b/java/com/google/gerrit/server/restapi/group/ListGroups.java
index b94e44d..4d9a1e9 100644
--- a/java/com/google/gerrit/server/restapi/group/ListGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListGroups.java
@@ -21,6 +21,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
@@ -408,6 +409,7 @@
}
}
+ @Nullable
private Pattern getRegexPattern() {
return Strings.isNullOrEmpty(matchRegex) ? null : Pattern.compile(matchRegex);
}
diff --git a/java/com/google/gerrit/server/restapi/project/BanCommit.java b/java/com/google/gerrit/server/restapi/project/BanCommit.java
index eb5473d..2dd7bd8 100644
--- a/java/com/google/gerrit/server/restapi/project/BanCommit.java
+++ b/java/com/google/gerrit/server/restapi/project/BanCommit.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.projects.BanCommitInput;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -64,6 +65,7 @@
return Response.ok(r);
}
+ @Nullable
private static List<String> transformCommits(List<ObjectId> commits) {
if (commits == null || commits.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
index 904a16f..192e624 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
@@ -17,6 +17,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
@@ -125,6 +126,7 @@
return info;
}
+ @Nullable
private static Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
ProjectState project,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
diff --git a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
index ca48109..455358a 100644
--- a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
@@ -225,6 +225,7 @@
return info;
}
+ @Nullable
private static String replace(String project, String input) {
return input == null ? input : input.replace("${project}", project);
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 651e7f0..e1a3c0c 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -26,6 +26,7 @@
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
@@ -340,6 +341,7 @@
return accessSectionInfo;
}
+ @Nullable
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
diff --git a/java/com/google/gerrit/server/rules/RulesCache.java b/java/com/google/gerrit/server/rules/RulesCache.java
index 773c75e..710c734 100644
--- a/java/com/google/gerrit/server/rules/RulesCache.java
+++ b/java/com/google/gerrit/server/rules/RulesCache.java
@@ -191,6 +191,7 @@
return pmc;
}
+ @Nullable
private PrologMachineCopy consultRules(String name, Reader rules) throws CompileException {
BufferingPrologControl ctl = newEmptyMachine(systemLoader);
PushbackReader in = new PushbackReader(rules, Prolog.PUSHBACK_SIZE);
diff --git a/java/com/google/gerrit/server/securestore/DefaultSecureStore.java b/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
index 02ff159..37e7278 100644
--- a/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
+++ b/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.securestore;
import com.google.gerrit.common.FileUtil;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
@@ -54,6 +55,7 @@
return sec.getStringList(section, subsection, name);
}
+ @Nullable
@Override
public synchronized String[] getListForPlugin(
String pluginName, String section, String subsection, String name) {
diff --git a/java/com/google/gerrit/server/securestore/SecureStore.java b/java/com/google/gerrit/server/securestore/SecureStore.java
index b53e38c..855c978 100644
--- a/java/com/google/gerrit/server/securestore/SecureStore.java
+++ b/java/com/google/gerrit/server/securestore/SecureStore.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.securestore;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import java.util.List;
/**
@@ -53,6 +54,7 @@
*
* @return decrypted String value or {@code null} if not found
*/
+ @Nullable
public final String get(String section, String subsection, String name) {
String[] values = getList(section, subsection, name);
if (values != null && values.length > 0) {
@@ -67,6 +69,7 @@
*
* @return decrypted String value or {@code null} if not found
*/
+ @Nullable
public final String getForPlugin(
String pluginName, String section, String subsection, String name) {
String[] values = getListForPlugin(pluginName, section, subsection, name);
diff --git a/java/com/google/gerrit/server/submit/CherryPick.java b/java/com/google/gerrit/server/submit/CherryPick.java
index b218347..0471b67 100644
--- a/java/com/google/gerrit/server/submit/CherryPick.java
+++ b/java/com/google/gerrit/server/submit/CherryPick.java
@@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetInfo;
@@ -142,6 +143,7 @@
patchSetInfo = args.patchSetInfoFactory.get(ctx.getRevWalk(), newCommit, psId);
}
+ @Nullable
@Override
public PatchSet updateChangeImpl(ChangeContext ctx) throws NoSuchChangeException, IOException {
if (newCommit == null && toMerge.getStatusCode() == SKIPPED_IDENTICAL_TREE) {
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 27eb0a4..a34aeac 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -929,11 +929,13 @@
}
}
+ @Nullable
private SubmitType getSubmitType(ChangeData cd) {
SubmitTypeRecord str = cd.submitTypeRecord();
return str.isOk() ? str.type : null;
}
+ @Nullable
private OpenRepo openRepo(Project.NameKey project) {
try {
return orm.getRepo(project);
diff --git a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
index cfb2f88..5f58a74 100644
--- a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
@@ -20,6 +20,7 @@
import static com.google.gerrit.server.submit.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
@@ -238,6 +239,7 @@
acceptMergeTip(args.mergeTip);
}
+ @Nullable
@Override
public PatchSet updateChangeImpl(ChangeContext ctx)
throws NoSuchChangeException, ResourceConflictException, IOException, BadRequestException {
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index d06940c..bb2b1a4 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -22,6 +22,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.LabelId;
@@ -158,6 +159,7 @@
}
}
+ @Nullable
private CodeReviewCommit getAlreadyMergedCommit(RepoContext ctx) throws IOException {
CodeReviewCommit tip = args.mergeTip.getInitialTip();
if (tip == null) {
diff --git a/java/com/google/gerrit/server/submit/SubmoduleCommits.java b/java/com/google/gerrit/server/submit/SubmoduleCommits.java
index 37df66b..1fd3ad6 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleCommits.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleCommits.java
@@ -18,6 +18,7 @@
import static java.util.stream.Collectors.toList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.SubmoduleSubscription;
import com.google.gerrit.exceptions.StorageException;
@@ -212,6 +213,7 @@
return newCommit;
}
+ @Nullable
private RevCommit updateSubmodule(
DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
throws SubmoduleConflictException, IOException {
diff --git a/java/com/google/gerrit/server/util/MagicBranch.java b/java/com/google/gerrit/server/util/MagicBranch.java
index 924c288..a5ce108 100644
--- a/java/com/google/gerrit/server/util/MagicBranch.java
+++ b/java/com/google/gerrit/server/util/MagicBranch.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.util;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.entities.Project;
import java.io.IOException;
@@ -38,6 +39,7 @@
}
/** Returns the ref name prefix for a magic branch, {@code null} if the branch is not magic */
+ @Nullable
public static String getMagicRefNamePrefix(String refName) {
if (refName.startsWith(NEW_CHANGE)) {
return NEW_CHANGE;
diff --git a/java/com/google/gerrit/server/util/git/BUILD b/java/com/google/gerrit/server/util/git/BUILD
index bbc6bf0..83a230d 100644
--- a/java/com/google/gerrit/server/util/git/BUILD
+++ b/java/com/google/gerrit/server/util/git/BUILD
@@ -5,6 +5,7 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//lib:jgit",
"//lib/flogger:api",
diff --git a/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java b/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
index 97132a3..201a9b7 100644
--- a/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
+++ b/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.util.git;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmoduleSubscription;
@@ -65,6 +66,7 @@
return parsedSubscriptions;
}
+ @Nullable
private SubmoduleSubscription parse(String id) {
final String url = config.getString("submodule", id, "url");
final String path = config.getString("submodule", id, "path");
diff --git a/java/com/google/gerrit/sshd/Commands.java b/java/com/google/gerrit/sshd/Commands.java
index b6d3401..5d641a0 100644
--- a/java/com/google/gerrit/sshd/Commands.java
+++ b/java/com/google/gerrit/sshd/Commands.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.auto.value.AutoAnnotation;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Key;
import java.lang.annotation.Annotation;
import org.apache.sshd.server.command.Command;
@@ -78,6 +79,7 @@
return false;
}
+ @Nullable
static CommandName parentOf(CommandName name) {
if (name instanceof NestedCommandNameImpl) {
return ((NestedCommandNameImpl) name).parent;
diff --git a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 6997d96..401d31e 100644
--- a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -22,6 +22,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.FileUtil;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.account.AccountSshKey;
@@ -169,6 +170,7 @@
return p.keys;
}
+ @Nullable
private SshKeyCacheEntry find(Iterable<SshKeyCacheEntry> keyList, PublicKey suppliedKey) {
for (SshKeyCacheEntry k : keyList) {
if (k.match(suppliedKey)) {
diff --git a/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
index 5b6d8f9..7adcd24 100644
--- a/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
+++ b/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
@@ -19,6 +19,7 @@
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.server.plugins.InvalidPluginException;
import com.google.gerrit.server.plugins.ModuleGenerator;
@@ -84,6 +85,7 @@
listeners.put(tl, clazz);
}
+ @Nullable
@Override
public Module create() throws InvalidPluginException {
checkState(command != null, "pluginName must be provided");
diff --git a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
index 6e8590c..8711fe6 100644
--- a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
+++ b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.DynamicOptions;
import com.google.gerrit.server.plugins.Plugin;
@@ -57,6 +58,7 @@
}
}
+ @Nullable
private Provider<Command> load(Plugin plugin) {
if (plugin.getSshInjector() != null) {
Key<Command> key = Commands.key(plugin.getName());
diff --git a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index 4da55e2..2e29203 100644
--- a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -17,6 +17,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -87,6 +88,7 @@
}
}
+ @Nullable
private String readSshKey() throws IOException {
if (sshKey == null) {
return null;
diff --git a/java/com/google/gerrit/util/cli/CmdLineParser.java b/java/com/google/gerrit/util/cli/CmdLineParser.java
index 7c42797..a37c027 100644
--- a/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -44,6 +44,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.StringWriter;
@@ -567,6 +568,7 @@
* and it needed to be exposed.
*/
@SuppressWarnings("rawtypes")
+ @Nullable
public OptionHandler findOptionByName(String name) {
for (OptionHandler h : optionsList) {
if (h.option instanceof NamedOptionDef) {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index c2b779b..ec2e751 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -2064,6 +2064,7 @@
return newEmailInput(email, true);
}
+ @Nullable
private String getMetaId(Account.Id accountId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo);
diff --git a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index 18eca27..462d0a5 100644
--- a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -24,6 +24,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.extensions.api.plugins.InstallPluginInput;
import com.google.gerrit.extensions.api.plugins.PluginApi;
@@ -198,6 +199,7 @@
return pluginJarContent(plugin);
}
+ @Nullable
private String pluginVersion(String plugin) {
String name = pluginName(plugin);
if (name.endsWith("empty")) {
@@ -210,6 +212,7 @@
return dash > 0 ? name.substring(dash + 1) : "";
}
+ @Nullable
private String pluginApiVersion(String plugin) {
if (plugin.endsWith("normal.jar")) {
return "2.16.19-SNAPSHOT";
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java b/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
index 6d2c6dfa..a9e3cf6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
@@ -18,6 +18,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
import java.util.List;
@@ -41,6 +42,7 @@
userRestSession.get("/config/server/tasks/" + getLogFileCompressorTaskId()).assertNotFound();
}
+ @Nullable
private String getLogFileCompressorTaskId() throws Exception {
RestResponse r = adminRestSession.get("/config/server/tasks/");
List<TaskInfo> result =
diff --git a/javatests/com/google/gerrit/extensions/BUILD b/javatests/com/google/gerrit/extensions/BUILD
index 2202a11..1bb39c8 100644
--- a/javatests/com/google/gerrit/extensions/BUILD
+++ b/javatests/com/google/gerrit/extensions/BUILD
@@ -5,6 +5,7 @@
size = "small",
srcs = glob(["**/*.java"]),
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/extensions/common/testing:common-test-util",
"//lib:guava",
diff --git a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
index 024e35e..7ed236a 100644
--- a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
+++ b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.ReviewerState;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
@@ -344,6 +345,7 @@
assertThat(diff.removed().reviewers).isNull();
}
+ @Nullable
private static Object buildObjectWithFullFields(Class<?> c) throws Exception {
if (c == null) {
return null;
@@ -365,6 +367,7 @@
return toPopulate;
}
+ @Nullable
private static Class<?> getParameterizedType(Field field) {
if (!Collection.class.isAssignableFrom(field.getType())) {
return null;
diff --git a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
index 11f3528..2d90ab4 100644
--- a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
@@ -75,6 +75,7 @@
allUsersRepo.close();
}
+ @Nullable
protected Instant getTipTimestamp(AccountGroup.UUID uuid) throws Exception {
try (RevWalk rw = new RevWalk(allUsersRepo)) {
Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(uuid));
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 8def660..ca53bad 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -37,6 +37,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.AttentionSetUpdate;
@@ -3938,6 +3939,7 @@
return new String(rw.getObjectReader().open(dataId, OBJ_BLOB).getCachedBytes(), UTF_8);
}
+ @Nullable
private ObjectId exactRefAllUsers(String refName) throws Exception {
try (Repository allUsersRepo = repoManager.openRepository(allUsers)) {
Ref ref = allUsersRepo.exactRef(refName);
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 04e9367..a7edd0f 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
@@ -747,6 +748,7 @@
return "\"" + s + "\"";
}
+ @Nullable
protected String name(String name) {
if (name == null) {
return null;
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index c255f5d..c781d8b 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -13,6 +13,7 @@
"//prolog:gerrit-prolog-common",
],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
diff --git a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
index 4f48b9e..fe60119 100644
--- a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
@@ -109,7 +109,6 @@
@Test
@UseClockStep
- @SuppressWarnings("unchecked")
public void internalQueriesPaginate() throws Exception {
// create 4 changes
TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index e5c893d..540416f 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.fail;
import com.google.common.base.CharMatcher;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
@@ -560,6 +561,7 @@
return groups.stream().map(g -> g.id).sorted().collect(toList());
}
+ @Nullable
protected String name(String name) {
if (name == null) {
return null;
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index 0cc132d..e877c81 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -10,6 +10,7 @@
visibility = ["//visibility:public"],
runtime_deps = ["//java/com/google/gerrit/lucene"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index c06fcde..b119104 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -24,6 +24,7 @@
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
@@ -469,6 +470,7 @@
return projects.stream().map(p -> p.name).collect(toList());
}
+ @Nullable
protected String name(String name) {
if (name == null) {
return null;
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index f476ae6..53f9d9d 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -10,6 +10,7 @@
visibility = ["//visibility:public"],
runtime_deps = ["//java/com/google/gerrit/lucene"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
diff --git a/javatests/com/google/gerrit/util/http/testutil/BUILD b/javatests/com/google/gerrit/util/http/testutil/BUILD
index 3a67d45..3b4817b 100644
--- a/javatests/com/google/gerrit/util/http/testutil/BUILD
+++ b/javatests/com/google/gerrit/util/http/testutil/BUILD
@@ -6,6 +6,7 @@
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//lib:guava",
"//lib:jgit",
"//lib:servlet-api",
diff --git a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
index ebdf2d9..0347177 100644
--- a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
+++ b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
@@ -25,6 +25,7 @@
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.Nullable;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -105,6 +106,7 @@
return -1;
}
+ @Nullable
@Override
public String getContentType() {
List<String> contentType = headers.get("Content-Type");
diff --git a/plugins/download-commands b/plugins/download-commands
index 71331e1..a16ebc6 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 71331e15af5a62ee7b13dee6ebdadf23d7e75a40
+Subproject commit a16ebc6cdaaa4db5e5a2b6d062bb0ebbb3d3d0f4
diff --git a/plugins/gitiles b/plugins/gitiles
index 24529d2..12e26b3 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit 24529d232268ac51fd6850770f70dc0fcd732dd8
+Subproject commit 12e26b33ac55109bbb1d5eb56f198235552fb919
diff --git a/plugins/hooks b/plugins/hooks
index 20aeaa9..3007362 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 20aeaa9fcc6aa2665acb5e0f401039074037221c
+Subproject commit 30073628612bce23826f4be71bfdd159da521cbc
diff --git a/plugins/replication b/plugins/replication
index f1aefa2..ced31c0 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit f1aefa28f821699cc1ddd37bf0aa85177c775f17
+Subproject commit ced31c0c7d56cbb3e10a8da35a8ddd5db1bba550
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 4198fe8..10db2cf 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 4198fe8df1c1b86d812f32da63e891b1c2fc6f3e
+Subproject commit 10db2cf772989d031c6f3558010c51fe07cf9722
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 3239ce3..084a372 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 3239ce3a471f5aa9edd8f6f702bee655ea81f77d
+Subproject commit 084a37253dc94ac52cfaa1c9d516fcb8b0318b31
diff --git a/plugins/webhooks b/plugins/webhooks
index d8815bf..16110f3 160000
--- a/plugins/webhooks
+++ b/plugins/webhooks
@@ -1 +1 @@
-Subproject commit d8815bf9660b6655696db242b8ad2801e866c036
+Subproject commit 16110f320dd5b6a40af87eaba4bf3af60cb0efd1
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
index 0afb273..9c53fea 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import '../../shared/gr-button/gr-button';
-import '../../shared/gr-icons/gr-icons';
import {fireEvent} from '../../../utils/event-util';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, css, html} from 'lit';
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 88bf45e..917b7ca 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -8,7 +8,6 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-dropdown/gr-dropdown';
import '../../shared/gr-icon/gr-icon';
-import '../../shared/gr-icons/gr-icons';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-confirm-abandon-dialog/gr-confirm-abandon-dialog';
import '../gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index e0fcd88..d6e1958 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -15,7 +15,7 @@
import '../../shared/gr-change-star/gr-change-star';
import '../../shared/gr-change-status/gr-change-status';
import '../../shared/gr-editable-content/gr-editable-content';
-import '../../shared/gr-linked-text/gr-linked-text';
+import '../../shared/gr-formatted-text/gr-formatted-text';
import '../../shared/gr-overlay/gr-overlay';
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import '../gr-change-actions/gr-change-actions';
@@ -104,12 +104,7 @@
import {GrIncludedInDialog} from '../gr-included-in-dialog/gr-included-in-dialog';
import {GrDownloadDialog} from '../gr-download-dialog/gr-download-dialog';
import {GrChangeMetadata} from '../gr-change-metadata/gr-change-metadata';
-import {
- assertIsDefined,
- assert,
- query as queryEl,
- queryAll,
-} from '../../../utils/common-util';
+import {assertIsDefined, assert, queryAll} from '../../../utils/common-util';
import {GrEditControls} from '../../edit/gr-edit-controls/gr-edit-controls';
import {
CommentThread,
@@ -121,14 +116,12 @@
import {GrFileList} from '../gr-file-list/gr-file-list';
import {EditRevisionInfo, ParsedChangeInfo} from '../../../types/types';
import {
- ChecksTabState,
CloseFixPreviewEvent,
EditableContentSaveEvent,
EventType,
OpenFixPreviewEvent,
ShowAlertEventDetail,
SwitchTabEvent,
- SwitchTabEventDetail,
TabState,
ValueChangedEvent,
} from '../../../types/events';
@@ -486,9 +479,12 @@
@state()
private changeViewAriaHidden = false;
+ /**
+ * This can be a string only for plugin provided tabs.
+ */
// visible for testing
@state()
- activeTab = Tab.FILES;
+ activeTab: Tab | string = Tab.FILES;
@property({type: Boolean})
unresolvedOnly = true;
@@ -702,6 +698,11 @@
);
subscribe(
this,
+ () => this.getViewModel().tab$,
+ t => (this.activeTab = t ?? Tab.FILES)
+ );
+ subscribe(
+ this,
() => this.getChecksModel().aPluginHasRegistered$,
b => {
this.showChecksTab = b;
@@ -962,7 +963,7 @@
/* Account for border and padding and rounding errors. */
max-width: calc(72ch + 2px + 2 * var(--spacing-m) + 0.4px);
}
- .commitMessage gr-linked-text {
+ .commitMessage gr-formatted-text {
word-break: break-word;
}
#commitMessageEditor {
@@ -1463,12 +1464,10 @@
.commitCollapsible=${this.computeCommitCollapsible()}
remove-zero-width-space=""
>
- <gr-linked-text
- pre=""
- .content=${this.latestCommitMessage}
- .config=${this.projectConfig?.commentlinks}
- remove-zero-width-space=""
- ></gr-linked-text>
+ <gr-formatted-text
+ .content=${this.latestCommitMessage ?? ''}
+ .markdown=${false}
+ ></gr-formatted-text>
</gr-editable-content>
</div>
<h3 class="assistive-tech-only">Comments and Checks Summary</h3>
@@ -1499,7 +1498,10 @@
private renderTabHeaders() {
return html`
- <paper-tabs id="tabs" @selected-changed=${this.setActiveTab}>
+ <paper-tabs
+ id="tabs"
+ @selected-changed=${this.onPaperTabSelectionChanged}
+ >
<paper-tab @click=${this.onPaperTabClick} data-name=${Tab.FILES}
><span>Files</span></paper-tab
>
@@ -1732,38 +1734,38 @@
}
}
- setActiveTab(e: SwitchTabEvent) {
- const paperTabs = queryEl<PaperTabsElement>(this, '#tabs');
- if (!paperTabs) return;
- const tabs = [...queryAll<HTMLElement>(paperTabs, 'paper-tab')];
+ onPaperTabSelectionChanged(e: ValueChangedEvent) {
+ if (!this.tabs) return;
+ const tabs = [...queryAll<HTMLElement>(this.tabs, 'paper-tab')];
if (!tabs) return;
- let tabName = e.detail.tab;
- let tabIndex = e.detail.value;
+ const tabIndex = Number(e.detail.value);
+ assert(
+ Number.isInteger(tabIndex) && 0 <= tabIndex && tabIndex < tabs.length,
+ `${tabIndex} must be integer`
+ );
+ const tab = tabs[tabIndex].dataset['name'];
- if (tabIndex === undefined) {
- assert(tabName !== undefined, 'tabName or tabIndex must be defined');
- tabIndex = tabs.findIndex(t => t.dataset['name'] === tabName);
- assert(tabIndex !== -1, `tab ${tabName} not found`);
+ this.getViewModel().updateState({tab});
+ }
+
+ setActiveTab(e: SwitchTabEvent) {
+ if (!this.tabs) return;
+ const tabs = [...queryAll<HTMLElement>(this.tabs, 'paper-tab')];
+ if (!tabs) return;
+
+ const tab = e.detail.tab;
+ const tabIndex = tabs.findIndex(t => t.dataset['name'] === tab);
+ assert(tabIndex !== -1, `tab ${tab} not found`);
+
+ if (this.tabs.selected !== tabIndex) {
+ this.tabs.selected = tabIndex;
}
- if (tabName === undefined) {
- tabName = tabs[tabIndex].dataset['name'];
- }
-
- if (paperTabs.selected !== tabIndex) {
- // paperTabs.selected is undefined during rendering
- if (paperTabs.selected !== undefined) {
- const src = (e.composedPath()?.[0] as Element | undefined)?.tagName;
- this.reporting.reportInteraction(Interaction.SHOW_TAB, {tabName, src});
- }
- paperTabs.selected = tabIndex;
- }
-
- this.activeTab = tabName as Tab;
+ this.getViewModel().updateState({tab});
if (e.detail.tabState) this.tabState = e.detail.tabState;
- if (e.detail.scrollIntoView) paperTabs.scrollIntoView({block: 'center'});
+ if (e.detail.scrollIntoView) this.tabs.scrollIntoView({block: 'center'});
}
/**
@@ -2249,19 +2251,7 @@
} else if (this.viewState?.commentId) {
tab = Tab.COMMENT_THREADS;
}
- const detail: SwitchTabEventDetail = {
- tab,
- };
- if (tab === Tab.CHECKS) {
- const state: ChecksTabState = {};
- detail.tabState = {checksTab: state};
- }
-
- this.setActiveTab(
- new CustomEvent(EventType.SHOW_TAB, {
- detail,
- })
- );
+ this.setActiveTab(new CustomEvent(EventType.SHOW_TAB, {detail: {tab}}));
}
// Private but used in tests.
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index ad84fb0..f3017f8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -433,9 +433,7 @@
id="commitMessageEditor"
remove-zero-width-space=""
>
- <gr-linked-text pre="" remove-zero-width-space="">
- <span id="output" slot="insert"></span>
- </gr-linked-text>
+ <gr-formatted-text></gr-formatted-text>
</gr-editable-content>
</div>
<h3 class="assistive-tech-only">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index 76335cb..a7d9e28 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -342,8 +342,8 @@
});
test('correct number of files are shown', async () => {
- element.fileListIncrement = 300;
- element.files = createFiles(500);
+ element.fileListIncrement = 100;
+ element.files = createFiles(250);
await element.updateComplete;
await waitEventLoop();
@@ -358,19 +358,19 @@
element,
'#incrementButton'
).textContent!.trim(),
- 'Show 300 more'
+ 'Show 50 more'
);
assert.equal(
queryAndAssert<GrButton>(element, '#showAllButton').textContent!.trim(),
- 'Show all 500 files'
+ 'Show all 250 files'
);
queryAndAssert<GrButton>(element, '#showAllButton').click();
await element.updateComplete;
await waitEventLoop();
- assert.equal(element.numFilesShown, 500);
- assert.equal(element.shownFiles.length, 500);
+ assert.equal(element.numFilesShown, 250);
+ assert.equal(element.shownFiles.length, 250);
assert.isTrue(controlRow.classList.contains('invisible'));
});
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index a4e26fe..848948f 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -77,6 +77,7 @@
import {DropdownItem} from '../shared/gr-dropdown-list/gr-dropdown-list';
import './gr-checks-attempt';
import {createDiffUrl} from '../../models/views/diff';
+import {changeViewModelToken} from '../../models/views/change';
/**
* Firing this event sets the regular expression of the results filter.
@@ -756,7 +757,7 @@
filterInput?: HTMLInputElement;
@state()
- filterRegExp = new RegExp('');
+ filterRegExp = '';
/** All runs. Shown should only the selected/filtered ones. */
@property({attribute: false})
@@ -766,8 +767,8 @@
* Check names of runs that are selected in the runs panel. When this array
* is empty, then no run is selected and all runs should be shown.
*/
- @property({attribute: false})
- selectedRuns: string[] = [];
+ @state()
+ selectedRuns: Set<string> = new Set();
@state()
actions: Action[] = [];
@@ -808,6 +809,8 @@
*/
private isSectionExpandedByUser = new Map<Category, boolean>();
+ private readonly getViewModel = resolve(this, changeViewModelToken);
+
private readonly getChangeModel = resolve(this, changeModelToken);
private readonly getChecksModel = resolve(this, checksModelToken);
@@ -853,6 +856,16 @@
() => this.getChecksModel().someProvidersAreLoadingSelected$,
x => (this.someProvidersAreLoading = x)
);
+ subscribe(
+ this,
+ () => this.getViewModel().checksRunsSelected$,
+ x => (this.selectedRuns = x)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().checksResultsFilter$,
+ x => (this.filterRegExp = x)
+ );
}
static override get styles() {
@@ -1051,6 +1064,9 @@
protected override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
+ if (changedProperties.has('filterRegExp') && this.filterInput) {
+ this.filterInput.value = this.filterRegExp;
+ }
if (changedProperties.has('tabState') && this.tabState) {
const {statusOrCategory, checkName} = this.tabState;
if (isCategory(statusOrCategory)) {
@@ -1224,11 +1240,10 @@
}
private handleFilter(e: ChecksResultsFilterEvent) {
- if (!this.filterInput) return;
- const oldValue = this.filterInput.value ?? '';
const newValue = e.detail.filterRegExp ?? '';
- this.filterInput.value = oldValue === newValue ? '' : newValue;
- this.onFilterInputChange();
+ this.getViewModel().updateState({
+ checksResultsFilter: this.filterRegExp === newValue ? '' : newValue,
+ });
}
private renderAction(action?: Action) {
@@ -1289,10 +1304,7 @@
}
isRunSelected(run: {checkName: string}) {
- return (
- this.selectedRuns.length === 0 ||
- this.selectedRuns.includes(run.checkName)
- );
+ return this.selectedRuns.size === 0 || this.selectedRuns.has(run.checkName);
}
renderFilter() {
@@ -1300,10 +1312,11 @@
run =>
this.isRunSelected(run) && isAttemptSelected(this.selectedAttempt, run)
);
- if (this.selectedRuns.length === 0 && allResults(runs).length <= 3) {
- if (this.filterRegExp.source.length > 0) {
- this.filterRegExp = new RegExp('');
- }
+ if (
+ this.selectedRuns.size === 0 &&
+ allResults(runs).length <= 3 &&
+ this.filterRegExp === ''
+ ) {
return;
}
return html`
@@ -1325,7 +1338,9 @@
{},
{deduping: Deduping.EVENT_ONCE_PER_CHANGE}
);
- this.filterRegExp = new RegExp(this.filterInput.value, 'i');
+ this.getViewModel().updateState({
+ checksResultsFilter: this.filterInput.value,
+ });
}
renderSection(category: Category) {
@@ -1342,18 +1357,32 @@
],
[]
);
- const isSelection = this.selectedRuns.length > 0;
+ const isSelectionActive = this.selectedRuns.size > 0;
const selected = all.filter(result => this.isRunSelected(result));
- const filtered = selected.filter(result =>
- matches(result, this.filterRegExp)
- );
+ const re = new RegExp(this.filterRegExp, 'i');
+ const filtered = selected.filter(result => matches(result, re));
+ const isFilterActiveWithResults =
+ this.filterRegExp !== '' && filtered.length > 0;
+
+ // The logic for deciding whether to expand a section by default is a bit
+ // complicated, but we want to collapse empty and info/success sections by
+ // default for a clean and focused user experience. However, as soon as the
+ // user starts selecting or filtering we must take this into account and
+ // prefer to expand the sections.
let expanded = this.isSectionExpanded.get(category);
const expandedByUser = this.isSectionExpandedByUser.get(category) ?? false;
if (!expandedByUser || expanded === undefined) {
- expanded = selected.length > 0 && (isWarningOrError || isSelection);
+ // Note that we are using `selected` for `isEmpty` and not `filtered`,
+ // because if the filter is what makes a section empty, then we want to
+ // show an expanded section, which contains a message about this.
+ const isEmpty = selected.length === 0;
+ expanded =
+ !isEmpty &&
+ (isWarningOrError || isSelectionActive || isFilterActiveWithResults);
this.isSectionExpanded.set(category, expanded);
}
const expandedClass = expanded ? 'expanded' : 'collapsed';
+
const isShowAll = this.isShowAll.get(category) ?? false;
const resultCount = filtered.length;
const empty = resultCount === 0 ? 'empty' : '';
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 09a2558..128a9b0 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -44,7 +44,7 @@
} from '../../models/checks/checks-fakes';
import {assertIsDefined} from '../../utils/common-util';
import {modifierPressed, whenVisible} from '../../utils/dom-util';
-import {fireRunSelected, fireRunSelectionReset} from './gr-checks-util';
+import {fireRunSelected, RunSelectedEvent} from './gr-checks-util';
import {ChecksTabState} from '../../types/events';
import {charsOnly} from '../../utils/string-util';
import {getAppContext} from '../../services/app-context';
@@ -57,6 +57,7 @@
import {Interaction} from '../../constants/reporting';
import {Deduping} from '../../api/reporting';
import {when} from 'lit/directives/when.js';
+import {changeViewModelToken} from '../../models/views/change';
@customElement('gr-checks-run')
export class GrChecksRun extends LitElement {
@@ -403,8 +404,8 @@
@property({type: Boolean, reflect: true})
collapsed = false;
- @property({attribute: false})
- selectedRuns: string[] = [];
+ @state()
+ selectedRuns: Set<string> = new Set();
@state()
selectedAttempt: AttemptChoice = LATEST_ATTEMPT;
@@ -424,6 +425,8 @@
private getChecksModel = resolve(this, checksModelToken);
+ private readonly getViewModel = resolve(this, changeViewModelToken);
+
private readonly reporting = getAppContext().reportingService;
constructor() {
@@ -453,6 +456,11 @@
() => this.getChecksModel().runFilterRegexp$,
x => (this.filterRegExp = x)
);
+ subscribe(
+ this,
+ () => this.getViewModel().checksRunsSelected$,
+ x => (this.selectedRuns = x)
+ );
this.addEventListener('click', () => {
if (this.collapsed) this.toggleCollapsed();
});
@@ -660,8 +668,8 @@
private renderTitleButtons() {
if (this.collapsed) return;
- if (this.selectedRuns.length < 2) return;
- const actions = this.selectedRuns.map(selected => {
+ if (this.selectedRuns.size < 2) return;
+ const actions = [...this.selectedRuns].map(selected => {
const run = this.runs.find(
run => run.isLatestAttempt && run.checkName === selected
);
@@ -676,7 +684,8 @@
<gr-button
class="font-normal"
link
- @click=${() => fireRunSelectionReset(this)}
+ @click=${() =>
+ this.getViewModel().updateState({checksRunsSelected: undefined})}
>Unselect All</gr-button
>
<gr-tooltip-content
@@ -820,16 +829,23 @@
}
renderRun(run: CheckRun) {
- const selectedRun = this.selectedRuns.includes(run.checkName);
- const deselected = !selectedRun && this.selectedRuns.length > 0;
+ const selectedRun = this.selectedRuns.has(run.checkName);
+ const deselected = !selectedRun && this.selectedRuns.size > 0;
return html`<gr-checks-run
.run=${run}
?condensed=${this.collapsed}
.selected=${selectedRun}
.deselected=${deselected}
+ @run-selected=${this.handleRunSelected}
></gr-checks-run>`;
}
+ handleRunSelected(e: RunSelectedEvent) {
+ if (e.detail.checkName) {
+ this.getViewModel().toggleSelectedCheckRun(e.detail.checkName);
+ }
+ }
+
showFilter(): boolean {
if (this.collapsed) return false;
return this.runs.length > 10 || !!this.filterRegExp;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 82893bc..c1bcdc1 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -14,7 +14,6 @@
import './gr-checks-runs';
import './gr-checks-results';
import {NumericChangeId, PatchSetNumber} from '../../types/common';
-import {RunSelectedEvent} from './gr-checks-util';
import {TabState} from '../../types/events';
import {getAppContext} from '../../services/app-context';
import {subscribe} from '../lit/subscription-controller';
@@ -50,9 +49,6 @@
@state()
changeNum: NumericChangeId | undefined = undefined;
- @state()
- selectedRuns: string[] = [];
-
private readonly getChangeModel = resolve(this, changeModelToken);
private readonly getChecksModel = resolve(this, checksModelToken);
@@ -132,41 +128,16 @@
class="runs"
?collapsed=${this.offsetWidth < 1000}
.runs=${this.runs}
- .selectedRuns=${this.selectedRuns}
.tabState=${this.tabState?.checksTab}
- @run-selected=${this.handleRunSelected}
></gr-checks-runs>
<gr-checks-results
class="results"
.tabState=${this.tabState?.checksTab}
.runs=${this.runs}
- .selectedRuns=${this.selectedRuns}
></gr-checks-results>
</div>
`;
}
-
- handleRunSelected(e: RunSelectedEvent) {
- this.reporting.reportInteraction(Interaction.CHECKS_RUN_SELECTED, {
- checkName: e.detail.checkName,
- reset: e.detail.reset,
- });
- if (e.detail.reset) {
- this.selectedRuns = [];
- return;
- }
- if (e.detail.checkName) {
- this.toggleSelected(e.detail.checkName);
- }
- }
-
- toggleSelected(checkName: string) {
- if (this.selectedRuns.includes(checkName)) {
- this.selectedRuns = this.selectedRuns.filter(r => r !== checkName);
- } else {
- this.selectedRuns = [...this.selectedRuns, checkName];
- }
- }
}
declare global {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-util.ts b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
index 939a87e..c7477c4 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-util.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-util.ts
@@ -11,7 +11,6 @@
} from '../../models/checks/checks-util';
export interface RunSelectedEventDetail {
- reset: boolean;
checkName?: string;
}
@@ -33,16 +32,6 @@
);
}
-export function fireRunSelectionReset(target: EventTarget) {
- target.dispatchEvent(
- new CustomEvent('run-selected', {
- detail: {reset: true},
- composed: true,
- bubbles: true,
- })
- );
-}
-
export function isAttemptSelected(
selectedAttempt: AttemptChoice,
run: CheckRun
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 74589d3..f829313 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -1406,8 +1406,12 @@
}
const filter = queryMap.get('filter');
if (filter) state.filter = filter;
+ const checksResultsFilter = queryMap.get('checksResultsFilter');
+ if (checksResultsFilter) state.checksResultsFilter = checksResultsFilter;
const attempt = stringToAttemptChoice(queryMap.get('attempt'));
if (attempt && attempt !== LATEST_ATTEMPT) state.attempt = attempt;
+ const selected = queryMap.get('checksRunsSelected');
+ if (selected) state.checksRunsSelected = new Set(selected.split(','));
assertIsDefined(state.project, 'project');
this.reporting.setRepoName(state.project);
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index ce3ed0d..119ba48 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -1149,6 +1149,8 @@
queryMap.set('filter', 'fff');
queryMap.set('select', 'sss');
queryMap.set('attempt', '1');
+ queryMap.set('checksRunsSelected', 'asdf,qwer');
+ queryMap.set('checksResultsFilter', 'asdf.*qwer');
ctx.querystring = queryMap.toString();
assertctxToParams(ctx, 'handleChangeRoute', {
view: GerritView.CHANGE,
@@ -1159,6 +1161,8 @@
attempt: 1,
filter: 'fff',
tab: 'checks',
+ checksRunsSelected: new Set(['asdf', 'qwer']),
+ checksResultsFilter: 'asdf.*qwer',
});
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 262f1fd..17d7516 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -22,7 +22,7 @@
import {PROVIDED_FIX_ID} from '../../../utils/comment-util';
import {OpenFixPreviewEvent} from '../../../types/events';
import {getAppContext} from '../../../services/app-context';
-import {fireCloseFixPreview, fireEvent} from '../../../utils/event-util';
+import {fireCloseFixPreview} from '../../../utils/event-util';
import {DiffLayer, ParsedChangeInfo} from '../../../types/types';
import {GrButton} from '../../shared/gr-button/gr-button';
import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
@@ -33,6 +33,7 @@
import {assert} from '../../../utils/common-util';
import {resolve} from '../../../models/dependency';
import {createChangeUrl} from '../../../models/views/change';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
interface FilePreview {
filepath: string;
@@ -44,6 +45,15 @@
@query('#applyFixOverlay')
applyFixOverlay?: GrOverlay;
+ @query('#applyFixDialog')
+ applyFixDialog?: GrDialog;
+
+ /** The currently observed dialog by `dialogOberserver`. */
+ observedDialog?: GrDialog;
+
+ /** The current observer observing the `observedDialog`. */
+ dialogObserver?: ResizeObserver;
+
@query('#nextFix')
nextFix?: GrButton;
@@ -102,9 +112,6 @@
this.diffPrefs = diffPreferences;
}
);
- this.addEventListener('diff-context-expanded', () => {
- if (this.applyFixOverlay) fireEvent(this.applyFixOverlay, 'iron-resize');
- });
}
static override styles = [
@@ -148,6 +155,39 @@
`;
}
+ override updated() {
+ this.updateDialogObserver();
+ }
+
+ override disconnectedCallback() {
+ this.removeDialogObserver();
+ super.disconnectedCallback();
+ }
+
+ private removeDialogObserver() {
+ this.dialogObserver?.disconnect();
+ this.dialogObserver = undefined;
+ this.observedDialog = undefined;
+ }
+
+ private updateDialogObserver() {
+ if (
+ this.applyFixDialog === this.observedDialog &&
+ this.dialogObserver !== undefined
+ ) {
+ return;
+ }
+
+ this.removeDialogObserver();
+ if (!this.applyFixDialog) return;
+
+ this.observedDialog = this.applyFixDialog;
+ this.dialogObserver = new ResizeObserver(() => {
+ this.applyFixOverlay?.refit();
+ });
+ this.dialogObserver.observe(this.observedDialog);
+ }
+
private renderHeader() {
return html`
<div slot="header">${this.currentFix?.description ?? ''}</div>
@@ -202,7 +242,7 @@
* Given event with fixSuggestions, fetch diffs associated with first
* suggested fix and open dialog.
*/
- async open(e: OpenFixPreviewEvent) {
+ open(e: OpenFixPreviewEvent) {
this.patchNum = e.detail.patchNum;
this.fixSuggestions = e.detail.fixSuggestions;
assert(this.fixSuggestions.length > 0, 'no fix in the event');
@@ -212,9 +252,6 @@
this.showSelectedFixSuggestion(this.fixSuggestions[0]),
this.applyFixOverlay?.open()
);
- return Promise.all(promises).then(() => {
- if (this.applyFixOverlay) fireEvent(this.applyFixOverlay, 'iron-resize');
- });
}
private async showSelectedFixSuggestion(fixSuggestion: FixSuggestionInfo) {
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
index b6e4a95..4d2d454 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
@@ -52,7 +52,7 @@
}
async function open(detail: OpenFixPreviewEventDetail) {
- await element.open(
+ element.open(
new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
detail,
})
diff --git a/polygerrit-ui/app/elements/lit/fit-controller.ts b/polygerrit-ui/app/elements/lit/fit-controller.ts
new file mode 100644
index 0000000..423a4f0
--- /dev/null
+++ b/polygerrit-ui/app/elements/lit/fit-controller.ts
@@ -0,0 +1,213 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {ReactiveController, ReactiveControllerHost} from 'lit';
+
+export interface FitControllerHost {
+ /**
+ * This offset will increase or decrease the distance to the left side
+ * of the screen, a negative offset will move the dropdown to the left
+ * a positive one, to the right.
+ *
+ */
+ horizontalOffset: number;
+
+ /**
+ * This offset will increase or decrease the distance to the top
+ * side of the screen: a negative offset will move the dropdown upwards
+ * , a positive one, downwards.
+ *
+ */
+ verticalOffset: number;
+}
+
+export interface PositionStyles {
+ top: string;
+ left: string;
+ position: string;
+ maxWidth: string;
+ maxHeight: string;
+ boxSizing: string;
+}
+
+/**
+ * `FitController` fits an element in another element using `max-height`
+ * and `max-width`.
+ *
+ * FitController overrides all properties defined in PositionStyles for the
+ * host.
+ * The element will only be sized and/or positioned if it has not already been
+ * sized and/or positioned by CSS.
+ * CSS properties | Action
+ * --------------------------|-------------------------------------------
+ * `position` set | Element is not centered horizontally/vertically
+ * `top` or `bottom` set | Element is not vertically centered
+ * `left` or `right` set | Element is not horizontally centered
+ * `max-height` set | Element respects `max-height`
+ * `max-width` set | Element respects `max-width`
+ *
+ * `FitController` positions an element into another element and gives it
+ * a horizontalAlignment = left and verticalAlignment = top.
+ * This will override the element's css position.
+ *
+ * Use `horizontalOffset, verticalOffset` to offset the element from its
+ * `positionTarget`; `FitController` will collapse these in order to
+ * keep the element within `window` boundaries, while preserving the element's
+ * CSS margin values.
+ *
+ */
+export class FitController implements ReactiveController {
+ host: ReactiveControllerHost & HTMLElement & FitControllerHost;
+
+ private originalStyles?: PositionStyles;
+
+ private positionTarget?: HTMLElement;
+
+ constructor(host: ReactiveControllerHost & HTMLElement & FitControllerHost) {
+ (this.host = host).addController(this);
+ }
+
+ hostConnected() {
+ this.positionTarget = this.getPositionTarget();
+ }
+
+ hostDisconnected() {}
+
+ // private but used in tests
+ getPositionTarget() {
+ let parent = this.host.parentNode;
+
+ if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ parent = (parent as ShadowRoot).host;
+ }
+
+ return parent as HTMLElement;
+ }
+
+ private saveOriginalStyles() {
+ // These properties are changed in position() hence keep the original
+ // values to reset the host styles later.
+ this.originalStyles = {
+ top: this.host.style.top || '',
+ left: this.host.style.left || '',
+ position: this.host.style.position || '',
+ maxWidth: this.host.style.maxWidth || '',
+ maxHeight: this.host.style.maxHeight || '',
+ boxSizing: this.host.style.boxSizing || '',
+ };
+ }
+
+ /**
+ * Reset the host style, and clear the memoized data.
+ */
+ private resetStyles() {
+ // It is necessary to clear the max-width:0px and max-height:0px.
+ // A component may call refit() multiple times, in which case we don't
+ // want the values assigned from the first call which may not be precisely
+ // correct to influence the second call.
+ // Hence we reset the styles here.
+ if (this.originalStyles !== undefined) {
+ Object.assign(this.host.style, this.originalStyles);
+ }
+ this.originalStyles = undefined;
+ }
+
+ setPositionTarget(target: HTMLElement) {
+ this.positionTarget = target;
+ }
+
+ /**
+ * Equivalent to calling `resetStyles()` and `position()`.
+ * Useful to call this after the element or the `window` element has
+ * been resized, or if any of the positioning properties
+ * (e.g. `horizontalOffset, verticalOffset`) are updated.
+ * It preserves the scroll position of the host.
+ */
+ refit() {
+ const scrollLeft = this.host.scrollLeft;
+ const scrollTop = this.host.scrollTop;
+ this.resetStyles();
+ this.position();
+ this.host.scrollLeft = scrollLeft;
+ this.host.scrollTop = scrollTop;
+ }
+
+ private position() {
+ this.saveOriginalStyles();
+
+ this.host.style.position = 'fixed';
+ // Need border-box for margin/padding.
+ this.host.style.boxSizing = 'border-box';
+
+ const hostRect = this.host.getBoundingClientRect();
+ const positionRect = this.getNormalizedRect(this.positionTarget!);
+ const windowRect = this.getNormalizedRect(window);
+
+ this.calculateAndSetPositions(hostRect, positionRect, windowRect);
+ }
+
+ // private but used in tests
+ calculateAndSetPositions(
+ hostRect: DOMRect,
+ positionRect: DOMRect,
+ windowRect: DOMRect
+ ) {
+ const hostStyles = (window as Window).getComputedStyle(this.host);
+ const hostMinWidth = parseInt(hostStyles.minWidth) || 0;
+ const hostMinHeight = parseInt(hostStyles.minHeight) || 0;
+
+ const hostMargin = {
+ top: parseInt(hostStyles.marginTop) || 0,
+ right: parseInt(hostStyles.marginRight) || 0,
+ bottom: parseInt(hostStyles.marginBottom) || 0,
+ left: parseInt(hostStyles.marginLeft) || 0,
+ };
+
+ let leftPosition =
+ positionRect.left + this.host.horizontalOffset + hostMargin.left;
+ let topPosition =
+ positionRect.top + this.host.verticalOffset + hostMargin.top;
+
+ // Limit right/bottom within window respecting the margin.
+ const rightPosition = Math.min(
+ windowRect.right - hostMargin.right,
+ leftPosition + hostRect.width
+ );
+ const bottomPosition = Math.min(
+ windowRect.bottom - hostMargin.bottom,
+ topPosition + hostRect.height
+ );
+
+ // Respect hostMinWidth and hostMinHeight
+ // Current width is rightPosition - leftPosition or hostRect.width
+ // rightPosition - leftPosition >= hostMinWidth
+ // => leftPosition <= rightPosition - hostMinWidth
+ leftPosition = Math.min(leftPosition, rightPosition - hostMinWidth);
+ topPosition = Math.min(topPosition, bottomPosition - hostMinHeight);
+
+ // Limit left/top within window respecting the margin.
+ leftPosition = Math.max(windowRect.left + hostMargin.left, leftPosition);
+ topPosition = Math.max(windowRect.top + hostMargin.top, topPosition);
+
+ // Use right/bottom to set maxWidth/maxHeight and respect
+ // minWidth/minHeight.
+ const maxWidth = Math.max(rightPosition - leftPosition, hostMinWidth);
+ const maxHeight = Math.max(bottomPosition - topPosition, hostMinHeight);
+
+ this.host.style.maxWidth = `${maxWidth}px`;
+ this.host.style.maxHeight = `${maxHeight}px`;
+
+ this.host.style.left = `${leftPosition}px`;
+ this.host.style.top = `${topPosition}px`;
+ }
+
+ private getNormalizedRect(target: Window | HTMLElement): DOMRect {
+ if (target === document.documentElement || target === window) {
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
+ }
+ return (target as HTMLElement).getBoundingClientRect();
+ }
+}
diff --git a/polygerrit-ui/app/elements/lit/fit-controller_test.ts b/polygerrit-ui/app/elements/lit/fit-controller_test.ts
new file mode 100644
index 0000000..2a60b83
--- /dev/null
+++ b/polygerrit-ui/app/elements/lit/fit-controller_test.ts
@@ -0,0 +1,143 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import '../../test/common-test-setup';
+import {fixture, html, assert} from '@open-wc/testing';
+import {FitController} from './fit-controller';
+import {LitElement} from 'lit';
+import {customElement} from 'lit/decorators.js';
+
+@customElement('fit-element')
+class FitElement extends LitElement {
+ fitController = new FitController(this);
+
+ horizontalOffset = 0;
+
+ verticalOffset = 0;
+
+ override render() {
+ return html`<div></div>`;
+ }
+}
+
+suite('fit controller', () => {
+ let element: FitElement;
+ setup(async () => {
+ element = await fixture(html`<fit-element></fit-element>`);
+ });
+
+ test('refit positioning', async () => {
+ const hostRect = new DOMRect(0, 0, 50, 50);
+
+ const positionRect = new DOMRect(37, 37, 300, 60);
+
+ const windowRect = new DOMRect(0, 0, 600, 600);
+
+ element.fitController.calculateAndSetPositions(
+ hostRect,
+ positionRect,
+ windowRect
+ );
+
+ assert.equal(element.style.top, '37px');
+ assert.equal(element.style.left, '37px');
+ });
+
+ test('refit positioning with offset', async () => {
+ const elementWithOffset: FitElement = await fixture(
+ html`<fit-element></fit-element>`
+ );
+ elementWithOffset.verticalOffset = 10;
+ elementWithOffset.horizontalOffset = 20;
+
+ const hostRect = new DOMRect(0, 0, 50, 50);
+
+ const positionRect = new DOMRect(37, 37, 300, 60);
+
+ const windowRect = new DOMRect(0, 0, 600, 600);
+
+ elementWithOffset.fitController.calculateAndSetPositions(
+ hostRect,
+ positionRect,
+ windowRect
+ );
+
+ assert.equal(elementWithOffset.style.top, '47px');
+ assert.equal(elementWithOffset.style.left, '57px');
+ });
+
+ test('host margin updates positioning', async () => {
+ const hostRect = new DOMRect(0, 0, 50, 50);
+
+ const positionRect = new DOMRect(37, 37, 300, 60);
+
+ const windowRect = new DOMRect(0, 0, 600, 600);
+
+ element.style.marginLeft = '10px';
+ element.style.marginTop = '10px';
+ element.fitController.calculateAndSetPositions(
+ hostRect,
+ positionRect,
+ windowRect
+ );
+
+ // is 10px extra from the previous test due to host margin
+ assert.equal(element.style.top, '47px');
+ assert.equal(element.style.left, '47px');
+ });
+
+ test('host minWidth, minHeight overrides positioning', async () => {
+ const hostRect = new DOMRect(0, 0, 50, 50);
+
+ const positionRect = new DOMRect(37, 37, 300, 60);
+
+ const windowRect = new DOMRect(0, 0, 600, 600);
+
+ element.style.marginLeft = '10px';
+ element.style.marginTop = '10px';
+
+ element.style.minHeight = '50px';
+ element.style.minWidth = '60px';
+
+ element.fitController.calculateAndSetPositions(
+ hostRect,
+ positionRect,
+ windowRect
+ );
+
+ assert.equal(element.style.top, '47px');
+
+ // Should be 47 like the previous test but that would make it overall
+ // smaller in width than the minWidth defined
+ assert.equal(element.style.left, '37px');
+ assert.equal(element.style.maxWidth, '60px');
+ });
+
+ test('positioning happens within window size ', async () => {
+ const hostRect = new DOMRect(0, 0, 50, 50);
+
+ const positionRect = new DOMRect(37, 37, 300, 60);
+
+ // window size is small hence limits the position
+ const windowRect = new DOMRect(0, 0, 50, 50);
+
+ element.style.marginLeft = '10px';
+ element.style.marginTop = '10px';
+
+ element.fitController.calculateAndSetPositions(
+ hostRect,
+ positionRect,
+ windowRect
+ );
+
+ assert.equal(element.style.top, '47px');
+ assert.equal(element.style.left, '47px');
+ // With the window size being 50, the element is styled with width 3px
+ // width = windowSize - leftPosition = 50 - 47 = 3px
+ // Without the window width restriction, in previous test maxWidth is 60px
+ assert.equal(element.style.maxWidth, '3px');
+ });
+});
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index 1250d49..e459c05 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -3,24 +3,16 @@
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import '@polymer/iron-dropdown/iron-dropdown';
import '../gr-cursor-manager/gr-cursor-manager';
import '../../../styles/shared-styles';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-autocomplete-dropdown_html';
-import {IronFitMixin} from '../../../mixins/iron-fit-mixin/iron-fit-mixin';
-import {customElement, property, observe} from '@polymer/decorators';
-import {IronFitBehavior} from '@polymer/iron-fit-behavior/iron-fit-behavior';
import {GrCursorManager} from '../gr-cursor-manager/gr-cursor-manager';
import {fireEvent} from '../../../utils/event-util';
import {addShortcut, Key} from '../../../utils/dom-util';
-
-export interface GrAutocompleteDropdown {
- $: {
- suggestions: Element;
- };
-}
+import {FitController} from '../../lit/fit-controller';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property, query} from 'lit/decorators.js';
+import {repeat} from 'lit/directives/repeat.js';
+import {sharedStyles} from '../../../styles/shared-styles';
declare global {
interface HTMLElementTagNameMap {
@@ -41,19 +33,8 @@
selected: HTMLElement | null;
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = IronFitMixin(PolymerElement, IronFitBehavior as IronFitBehavior);
-
-/**
- * @attr {String} vertical-align - inherited from IronOverlay
- * @attr {String} horizontal-align - inherited from IronOverlay
- */
@customElement('gr-autocomplete-dropdown')
-export class GrAutocompleteDropdown extends base {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrAutocompleteDropdown extends LitElement {
/**
* Fired when the dropdown is closed.
*
@@ -69,24 +50,84 @@
@property({type: Number})
index: number | null = null;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true, attribute: 'is-hidden'})
isHidden = true;
@property({type: Number})
- override verticalOffset: number | null = null;
+ verticalOffset = 0;
@property({type: Number})
- override horizontalOffset: number | null = null;
+ horizontalOffset = 0;
@property({type: Array})
suggestions: Item[] = [];
+ @query('#suggestions') suggestionsDiv?: HTMLDivElement;
+
/** Called in disconnectedCallback. */
private cleanups: (() => void)[] = [];
// visible for testing
cursor = new GrCursorManager();
+ // visible for testing
+ fitController = new FitController(this);
+
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ z-index: 100;
+ }
+ :host([is-hidden]) {
+ display: none;
+ }
+ ul {
+ list-style: none;
+ }
+ li {
+ border-bottom: 1px solid var(--border-color);
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-m) var(--spacing-l);
+ }
+ li:last-of-type {
+ border: none;
+ }
+ li:focus {
+ outline: none;
+ }
+ li:hover {
+ background-color: var(--hover-background-color);
+ }
+ li.selected {
+ background-color: var(--hover-background-color);
+ }
+ .dropdown-content {
+ background: var(--dropdown-background-color);
+ box-shadow: var(--elevation-level-2);
+ border-radius: var(--border-radius);
+ max-height: 50vh;
+ overflow: auto;
+ }
+ @media only screen and (max-height: 35em) {
+ .dropdown-content {
+ max-height: 80vh;
+ }
+ }
+ .label {
+ color: var(--deemphasized-text-color);
+ padding-left: var(--spacing-l);
+ }
+ .hide {
+ display: none;
+ }
+ `,
+ ];
+ }
+
constructor() {
super();
this.cursor.cursorTargetClass = 'selected';
@@ -95,20 +136,18 @@
override connectedCallback() {
super.connectedCallback();
+ this.cleanups.push(addShortcut(this, {key: Key.UP}, () => this.handleUp()));
this.cleanups.push(
- addShortcut(this, {key: Key.UP}, () => this._handleUp())
+ addShortcut(this, {key: Key.DOWN}, () => this.handleDown())
);
this.cleanups.push(
- addShortcut(this, {key: Key.DOWN}, () => this._handleDown())
+ addShortcut(this, {key: Key.ENTER}, () => this.handleEnter())
);
this.cleanups.push(
- addShortcut(this, {key: Key.ENTER}, () => this._handleEnter())
+ addShortcut(this, {key: Key.ESC}, () => this.handleEscape())
);
this.cleanups.push(
- addShortcut(this, {key: Key.ESC}, () => this._handleEscape())
- );
- this.cleanups.push(
- addShortcut(this, {key: Key.TAB}, () => this._handleTab())
+ addShortcut(this, {key: Key.TAB}, () => this.handleTab())
);
}
@@ -119,6 +158,51 @@
super.disconnectedCallback();
}
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('index')) {
+ this.setIndex();
+ }
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('suggestions')) {
+ this.onSuggestionsChanged();
+ }
+ }
+
+ override render() {
+ return html`
+ <div
+ class="dropdown-content"
+ slot="dropdown-content"
+ id="suggestions"
+ role="listbox"
+ >
+ <ul>
+ ${repeat(
+ this.suggestions,
+ (item, index) => html`
+ <li
+ data-index=${index}
+ data-value=${item.dataValue ?? ''}
+ tabindex="-1"
+ aria-label=${item.name ?? ''}
+ class="autocompleteOption"
+ role="option"
+ @click=${this.handleClickItem}
+ >
+ <span>${item.text}</span>
+ <span class="label ${this.computeLabelClass(item)}"
+ >${item.label}</span
+ >
+ </li>
+ `
+ )}
+ </ul>
+ </div>
+ `;
+ }
+
close() {
this.isHidden = true;
}
@@ -132,11 +216,15 @@
return this.getCursorTarget()?.dataset['value'] || '';
}
- _handleUp() {
+ setPositionTarget(target: HTMLElement) {
+ this.fitController?.setPositionTarget(target);
+ }
+
+ private handleUp() {
if (!this.isHidden) this.cursorUp();
}
- _handleDown() {
+ private handleDown() {
if (!this.isHidden) this.cursorDown();
}
@@ -148,7 +236,8 @@
if (!this.isHidden) this.cursor.previous();
}
- _handleTab() {
+ // private but used in tests
+ handleTab() {
this.dispatchEvent(
new CustomEvent<ItemSelectedEvent>('item-selected', {
detail: {
@@ -161,7 +250,8 @@
);
}
- _handleEnter() {
+ // private but used in tests
+ handleEnter() {
this.dispatchEvent(
new CustomEvent<ItemSelectedEvent>('item-selected', {
detail: {
@@ -174,12 +264,12 @@
);
}
- _handleEscape() {
- this._fireClose();
+ private handleEscape() {
+ this.fireClose();
this.close();
}
- _handleClickItem(e: Event) {
+ private handleClickItem(e: Event) {
e.preventDefault();
e.stopPropagation();
let selected = e.target! as HTMLElement;
@@ -201,7 +291,7 @@
);
}
- _fireClose() {
+ private fireClose() {
fireEvent(this, 'dropdown-closed');
}
@@ -209,32 +299,29 @@
return this.cursor.target;
}
- @observe('suggestions')
onSuggestionsChanged() {
if (this.suggestions.length > 0) {
if (!this.isHidden) {
- flush();
this.cursor.stops = Array.from(
- this.$.suggestions.querySelectorAll('li')
+ this.suggestionsDiv?.querySelectorAll('li') ?? []
);
- this._resetCursorIndex();
+ this.resetCursorIndex();
}
} else {
this.cursor.stops = [];
}
- this.refit();
+ this.fitController?.refit();
}
- @observe('index')
- _setIndex() {
+ private setIndex() {
this.cursor.index = this.index || -1;
}
- _resetCursorIndex() {
+ private resetCursorIndex() {
this.cursor.setCursorAtIndex(0);
}
- _computeLabelClass(item: Item) {
+ private computeLabelClass(item: Item) {
return item.label ? '' : 'hide';
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts
deleted file mode 100644
index e10d356..0000000
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_html.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- :host {
- z-index: 100;
- }
- :host([is-hidden]) {
- display: none;
- }
- ul {
- list-style: none;
- }
- li {
- border-bottom: 1px solid var(--border-color);
- cursor: pointer;
- display: flex;
- justify-content: space-between;
- padding: var(--spacing-m) var(--spacing-l);
- }
- li:last-of-type {
- border: none;
- }
- li:focus {
- outline: none;
- }
- li:hover {
- background-color: var(--hover-background-color);
- }
- li.selected {
- background-color: var(--hover-background-color);
- }
- .dropdown-content {
- background: var(--dropdown-background-color);
- box-shadow: var(--elevation-level-2);
- border-radius: var(--border-radius);
- max-height: 50vh;
- overflow: auto;
- }
- @media only screen and (max-height: 35em) {
- .dropdown-content {
- max-height: 80vh;
- }
- }
- .label {
- color: var(--deemphasized-text-color);
- padding-left: var(--spacing-l);
- }
- .hide {
- display: none;
- }
- </style>
- <div
- class="dropdown-content"
- slot="dropdown-content"
- id="suggestions"
- role="listbox"
- >
- <ul>
- <template is="dom-repeat" items="[[suggestions]]">
- <li
- data-index$="[[index]]"
- data-value$="[[item.dataValue]]"
- tabindex="-1"
- aria-label$="[[item.name]]"
- class="autocompleteOption"
- role="option"
- on-click="_handleClickItem"
- >
- <span>[[item.text]]</span>
- <span class$="label [[_computeLabelClass(item)]]"
- >[[item.label]]</span
- >
- </li>
- </template>
- </ul>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
index a75fca2..9a0ed5e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
@@ -11,6 +11,7 @@
queryAll,
queryAndAssert,
waitEventLoop,
+ waitUntil,
} from '../../../test/test-utils';
import {assertIsDefined} from '../../../utils/common-util';
import {fixture, html, assert} from '@open-wc/testing';
@@ -70,9 +71,6 @@
<span> 2 </span>
<span class="hide label"> </span>
</li>
- <dom-repeat style="display: none;">
- <template is="dom-repeat"> </template>
- </dom-repeat>
</ul>
</div>
`
@@ -93,7 +91,7 @@
});
test('tab key', () => {
- const handleTabSpy = sinon.spy(element, '_handleTab');
+ const handleTabSpy = sinon.spy(element, 'handleTab');
const itemSelectedStub = sinon.stub();
element.addEventListener('item-selected', itemSelectedStub);
pressKey(element, Key.TAB);
@@ -107,7 +105,7 @@
});
test('enter key', () => {
- const handleEnterSpy = sinon.spy(element, '_handleEnter');
+ const handleEnterSpy = sinon.spy(element, 'handleEnter');
const itemSelectedStub = sinon.stub();
element.addEventListener('item-selected', itemSelectedStub);
pressKey(element, Key.ENTER);
@@ -171,9 +169,9 @@
});
});
- test('updated suggestions resets cursor stops', () => {
+ test('updated suggestions resets cursor stops', async () => {
const resetStopsSpy = sinon.spy(element, 'onSuggestionsChanged');
element.suggestions = [];
- assert.isTrue(resetStopsSpy.called);
+ await waitUntil(() => resetStopsSpy.called);
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index 90fe32e..34e67b9 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -312,9 +312,7 @@
</div>
</paper-input>
<gr-autocomplete-dropdown
- vertical-align="top"
.verticalOffset=${this.verticalOffset}
- horizontal-align="left"
id="suggestions"
@item-selected=${this.handleItemSelect}
@dropdown-closed=${this.focusWithoutDisplayingSuggestions}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
index 9be28e7..d991180 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
@@ -48,12 +48,10 @@
</div>
</paper-input>
<gr-autocomplete-dropdown
- horizontal-align="left"
id="suggestions"
is-hidden=""
role="listbox"
style="position: fixed; top: 300px; left: 392.5px; box-sizing: border-box; max-height: 600px; max-width: 785px;"
- vertical-align="top"
>
</gr-autocomplete-dropdown>
`,
@@ -99,12 +97,7 @@
<slot name="suffix"> </slot>
</div>
</paper-input>
- <gr-autocomplete-dropdown
- horizontal-align="left"
- id="suggestions"
- role="listbox"
- vertical-align="top"
- >
+ <gr-autocomplete-dropdown id="suggestions" role="listbox">
</gr-autocomplete-dropdown>
`,
{
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 6ab0ec4..5a1db30 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -16,11 +16,7 @@
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
import {CommentLinks, EmailAddress} from '../../../api/rest-api';
-import {
- applyHtmlRewritesFromConfig,
- applyLinkRewritesFromConfig,
- linkifyNormalUrls,
-} from '../../../utils/link-util';
+import {linkifyUrlsAndApplyRewrite} from '../../../utils/link-util';
import '../gr-account-chip/gr-account-chip';
import {KnownExperimentId} from '../../../services/flags/flags';
import {getAppContext} from '../../../services/app-context';
@@ -125,7 +121,7 @@
}
private renderAsPlaintext() {
- const linkedText = this.rewriteText(
+ const linkedText = linkifyUrlsAndApplyRewrite(
htmlEscape(this.content).toString(),
this.repoCommentLinks
);
@@ -140,7 +136,7 @@
// renderer so we wrap 'this.rewriteText' so that 'this' is preserved via
// closure.
const boundRewriteText = (text: string) =>
- this.rewriteText(text, this.repoCommentLinks);
+ linkifyUrlsAndApplyRewrite(text, this.repoCommentLinks);
// We are overriding some marked-element renderers for a few reasons:
// 1. Disable inline images as a design/policy choice.
@@ -201,24 +197,6 @@
return text;
}
- private rewriteText(text: string, repoCommentLinks: CommentLinks) {
- // Turn universally identifiable URLs into links. Ex: www.google.com. The
- // markdown library inside marked-element does this too, but is more
- // conservative and misses some URLs like "google.com" without "www" prefix.
- text = linkifyNormalUrls(text);
-
- // Apply the host's config-specific regex replacements to create links. Ex:
- // link "Bug 12345" to "google.com/bug/12345"
- text = applyLinkRewritesFromConfig(text, repoCommentLinks);
-
- // Apply the host's config-specific regex replacements to write arbitrary
- // html. Most examples seen in the wild are also used for linking but with
- // finer control over the rendered text. Ex: "Bug 12345" => "#12345"
- text = applyHtmlRewritesFromConfig(text, repoCommentLinks);
-
- return text;
- }
-
override updated() {
// Look for @mentions and replace them with an account-label chip.
if (this.flagsService.isEnabled(KnownExperimentId.MENTION_USERS)) {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 6391347..ca49831 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -77,6 +77,76 @@
await element.updateComplete;
});
+ test('does not apply rewrites within links', async () => {
+ element.content = 'google.com/LinkRewriteMe';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <pre class="plaintext">
+ <a
+ href="http://google.com/LinkRewriteMe"
+ rel="noopener"
+ target="_blank"
+ >
+ google.com/LinkRewriteMe
+ </a>
+ </pre>
+ `
+ );
+ });
+
+ test('does not apply rewrites on rewritten text', async () => {
+ await setCommentLinks({
+ capitalizeFoo: {
+ match: 'foo',
+ html: 'FOO',
+ },
+ lowercaseFoo: {
+ match: 'FOO',
+ html: 'foo',
+ },
+ });
+ element.content = 'foo';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <pre class="plaintext">
+ FOO
+ </pre
+ >
+ `
+ );
+ });
+
+ test('supports overlapping rewrites', async () => {
+ await setCommentLinks({
+ bracketNum: {
+ match: '(Start:) ([0-9]+)',
+ html: '$1 [$2]',
+ },
+ bracketNum2: {
+ match: '(Start: [0-9]+) ([0-9]+)',
+ html: '$1 [$2]',
+ },
+ });
+ element.content = 'Start: 123 456';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <pre class="plaintext">
+ Start: [123] [456]
+ </pre
+ >
+ `
+ );
+ });
+
test('renders text with links and rewrites', async () => {
element.content = `text with plain link: google.com
\ntext with config link: LinkRewriteMe
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
deleted file mode 100644
index 16a60e7..0000000
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * @license
- * Copyright 2015 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../styles/shared-styles';
-import {GrLinkTextParser, LinkTextParserConfig} from './link-text-parser';
-import {LitElement, css, html, PropertyValues} from 'lit';
-import {customElement, property} from 'lit/decorators.js';
-import {assertIsDefined} from '../../../utils/common-util';
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-linked-text': GrLinkedText;
- }
-}
-
-@customElement('gr-linked-text')
-export class GrLinkedText extends LitElement {
- private outputElement?: HTMLSpanElement;
-
- @property({type: Boolean, attribute: 'remove-zero-width-space'})
- removeZeroWidthSpace?: boolean;
-
- @property({type: String})
- content = '';
-
- @property({type: Boolean, attribute: true})
- pre = false;
-
- @property({type: Boolean, attribute: true})
- disabled = false;
-
- @property({type: Boolean, attribute: true})
- inline = false;
-
- @property({type: Object})
- config?: LinkTextParserConfig;
-
- static override get styles() {
- return css`
- :host {
- display: block;
- }
- :host([inline]) {
- display: inline;
- }
- :host([pre]) ::slotted(span) {
- white-space: var(--linked-text-white-space, pre-wrap);
- word-wrap: var(--linked-text-word-wrap, break-word);
- }
- `;
- }
-
- override render() {
- return html`<slot name="insert"></slot>`;
- }
-
- // NOTE: LinkTextParser dynamically creates HTML fragments based on backend
- // configuration commentLinks. These commentLinks can contain arbitrary HTML
- // fragments. This means that arbitrary HTML needs to be injected into the
- // DOM-tree, where this HTML is is controlled on the server-side in the
- // server-configuration rather than by arbitrary users.
- // To enable this injection of 'unsafe' HTML, LinkTextParser generates
- // HTML fragments. Lit does not support inserting html fragments directly
- // into its DOM-tree as it controls the DOM-tree that it generates.
- // Therefore, to get around this we create a single element that we slot into
- // the Lit-owned DOM. This element will not be part of this LitElement as
- // it's slotted in and thus can be modified on the fly by handleParseResult.
- override firstUpdated(_changedProperties: PropertyValues): void {
- this.outputElement = document.createElement('span');
- this.outputElement.id = 'output';
- this.outputElement.slot = 'insert';
- this.append(this.outputElement);
- }
-
- override updated(changedProperties: PropertyValues): void {
- if (changedProperties.has('content') || changedProperties.has('config')) {
- this._contentOrConfigChanged();
- } else if (changedProperties.has('disabled')) {
- this.styleLinks();
- }
- }
-
- /**
- * Because either the source text or the linkification config has changed,
- * the content should be re-parsed.
- * Private but used in tests.
- *
- * @param content The raw, un-linkified source string to parse.
- * @param config The server config specifying commentLink patterns
- */
- _contentOrConfigChanged() {
- if (!this.config) {
- assertIsDefined(this.outputElement);
- this.outputElement.textContent = this.content;
- return;
- }
-
- assertIsDefined(this.outputElement);
- this.outputElement.textContent = '';
- const parser = new GrLinkTextParser(
- this.config,
- (text: string | null, href: string | null, fragment?: DocumentFragment) =>
- this.handleParseResult(text, href, fragment),
- this.removeZeroWidthSpace
- );
- parser.parse(this.content);
-
- // Ensure that external links originating from HTML commentlink configs
- // open in a new tab. @see Issue 5567
- // Ensure links to the same host originating from commentlink configs
- // open in the same tab. When target is not set - default is _self
- // @see Issue 4616
- this.outputElement.querySelectorAll('a').forEach(anchor => {
- if (anchor.hostname === window.location.hostname) {
- anchor.removeAttribute('target');
- } else {
- anchor.setAttribute('target', '_blank');
- }
- anchor.setAttribute('rel', 'noopener');
- });
-
- this.styleLinks();
- }
-
- /**
- * Styles the links based on whether gr-linked-text is disabled or not
- */
- private styleLinks() {
- assertIsDefined(this.outputElement);
- this.outputElement.querySelectorAll('a').forEach(anchor => {
- anchor.setAttribute('style', this.computeLinkStyle());
- });
- }
-
- private computeLinkStyle() {
- if (this.disabled) {
- return `
- color: inherit;
- text-decoration: none;
- pointer-events: none;
- `;
- } else {
- return 'color: var(--link-color)';
- }
- }
-
- /**
- * This method is called when the GrLikTextParser emits a partial result
- * (used as the "callback" parameter). It will be called in either of two
- * ways:
- * - To create a link: when called with `text` and `href` arguments, a link
- * element should be created and attached to the resulting DOM.
- * - To attach an arbitrary fragment: when called with only the `fragment`
- * argument, the fragment should be attached to the resulting DOM as is.
- */
- private handleParseResult(
- text: string | null,
- href: string | null,
- fragment?: DocumentFragment
- ) {
- assertIsDefined(this.outputElement);
- const output = this.outputElement;
- if (href) {
- const a = document.createElement('a');
- a.setAttribute('href', href);
- // GrLinkTextParser either pass text and href together or
- // only DocumentFragment - see LinkTextParserCallback
- a.textContent = text!;
- a.target = '_blank';
- a.setAttribute('rel', 'noopener');
- output.appendChild(a);
- } else if (fragment) {
- output.appendChild(fragment);
- }
- }
-}
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
deleted file mode 100644
index 00e0313..0000000
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
+++ /dev/null
@@ -1,471 +0,0 @@
-/**
- * @license
- * Copyright 2015 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup';
-import './gr-linked-text';
-import {fixture, html, assert} from '@open-wc/testing';
-import {GrLinkedText} from './gr-linked-text';
-import {queryAndAssert} from '../../../test/test-utils';
-
-suite('gr-linked-text tests', () => {
- let element: GrLinkedText;
-
- let originalCanonicalPath: string | undefined;
-
- setup(async () => {
- originalCanonicalPath = window.CANONICAL_PATH;
- element = await fixture<GrLinkedText>(html`
- <gr-linked-text>
- <div id="output"></div>
- </gr-linked-text>
- `);
-
- element.config = {
- ph: {
- match: '([Bb]ug|[Ii]ssue)\\s*#?(\\d+)',
- link: 'https://bugs.chromium.org/p/gerrit/issues/detail?id=$2',
- },
- prefixsameinlinkandpattern: {
- match: '([Hh][Tt][Tt][Pp]example)\\s*#?(\\d+)',
- link: 'https://bugs.chromium.org/p/gerrit/issues/detail?id=$2',
- },
- changeid: {
- match: '(I[0-9a-f]{8,40})',
- link: '#/q/$1',
- },
- changeid2: {
- match: 'Change-Id: +(I[0-9a-f]{8,40})',
- link: '#/q/$1',
- },
- googlesearch: {
- match: 'google:(.+)',
- link: 'https://bing.com/search?q=$1', // html should supersede link.
- html: '<a href="https://google.com/search?q=$1">$1</a>',
- },
- hashedhtml: {
- match: 'hash:(.+)',
- html: '<a href="#/awesomesauce">$1</a>',
- },
- baseurl: {
- match: 'test (.+)',
- html: '<a href="/r/awesomesauce">$1</a>',
- },
- anotatstartwithbaseurl: {
- match: 'a test (.+)',
- html: '[Lookup: <a href="/r/awesomesauce">$1</a>]',
- },
- disabledconfig: {
- match: 'foo:(.+)',
- link: 'https://google.com/search?q=$1',
- enabled: false,
- },
- };
- });
-
- teardown(() => {
- window.CANONICAL_PATH = originalCanonicalPath;
- });
-
- test('render', async () => {
- element.content =
- 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
- await element.updateComplete;
- assert.lightDom.equal(
- element,
- /* HTML */ `
- <div id="output"></div>
- <span id="output" slot="insert">
- <a
- href="https://bugs.chromium.org/p/gerrit/issues/detail?id=3650"
- rel="noopener"
- style="color: var(--link-color)"
- target="_blank"
- >
- https://bugs.chromium.org/p/gerrit/issues/detail?id=3650
- </a>
- </span>
- `
- );
- });
-
- test('URL pattern was parsed and linked.', async () => {
- // Regular inline link.
- const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
- element.content = url;
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.equal(linkEl.target, '_blank');
- assert.equal(linkEl.rel, 'noopener');
- assert.equal(linkEl.href, url);
- assert.equal(linkEl.textContent, url);
- });
-
- test('Bug pattern was parsed and linked', async () => {
- // "Issue/Bug" pattern.
- element.content = 'Issue 3650';
- await element.updateComplete;
-
- let linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
- assert.equal(linkEl.target, '_blank');
- assert.equal(linkEl.href, url);
- assert.equal(linkEl.textContent, 'Issue 3650');
-
- element.content = 'Bug 3650';
- await element.updateComplete;
-
- linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.equal(linkEl.target, '_blank');
- assert.equal(linkEl.rel, 'noopener');
- assert.equal(linkEl.href, url);
- assert.equal(linkEl.textContent, 'Bug 3650');
- });
-
- test('Pattern with same prefix as link was correctly parsed', async () => {
- // Pattern starts with the same prefix (`http`) as the url.
- element.content = 'httpexample 3650';
- await element.updateComplete;
-
- assert.equal(queryAndAssert(element, 'span#output').childNodes.length, 1);
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
- assert.equal(linkEl.target, '_blank');
- assert.equal(linkEl.href, url);
- assert.equal(linkEl.textContent, 'httpexample 3650');
- });
-
- test('Change-Id pattern was parsed and linked', async () => {
- // "Change-Id:" pattern.
- const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- const prefix = 'Change-Id: ';
- element.content = prefix + changeID;
- await element.updateComplete;
-
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[1] as HTMLAnchorElement;
- assert.equal(textNode.textContent, prefix);
- const url = '/q/' + changeID;
- assert.isFalse(linkEl.hasAttribute('target'));
- // Since url is a path, the host is added automatically.
- assert.isTrue(linkEl.href.endsWith(url));
- assert.equal(linkEl.textContent, changeID);
- });
-
- test('Change-Id pattern was parsed and linked with base url', async () => {
- window.CANONICAL_PATH = '/r';
-
- // "Change-Id:" pattern.
- const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- const prefix = 'Change-Id: ';
- element.content = prefix + changeID;
- await element.updateComplete;
-
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[1] as HTMLAnchorElement;
- assert.equal(textNode.textContent, prefix);
- const url = '/r/q/' + changeID;
- assert.isFalse(linkEl.hasAttribute('target'));
- // Since url is a path, the host is added automatically.
- assert.isTrue(linkEl.href.endsWith(url));
- assert.equal(linkEl.textContent, changeID);
- });
-
- test('Multiple matches', async () => {
- element.content = 'Issue 3650\nIssue 3450';
- await element.updateComplete;
-
- const linkEl1 = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- const linkEl2 = queryAndAssert(element, 'span#output')
- .childNodes[2] as HTMLAnchorElement;
-
- assert.equal(linkEl1.target, '_blank');
- assert.equal(
- linkEl1.href,
- 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650'
- );
- assert.equal(linkEl1.textContent, 'Issue 3650');
-
- assert.equal(linkEl2.target, '_blank');
- assert.equal(
- linkEl2.href,
- 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3450'
- );
- assert.equal(linkEl2.textContent, 'Issue 3450');
- });
-
- test('Change-Id pattern parsed before bug pattern', async () => {
- // "Change-Id:" pattern.
- const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- const prefix = 'Change-Id: ';
-
- // "Issue/Bug" pattern.
- const bug = 'Issue 3650';
-
- const changeUrl = '/q/' + changeID;
- const bugUrl = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
-
- element.content = prefix + changeID + bug;
- await element.updateComplete;
-
- const textNode = queryAndAssert(element, 'span#output').childNodes[0];
- const changeLinkEl = queryAndAssert(element, 'span#output')
- .childNodes[1] as HTMLAnchorElement;
- const bugLinkEl = queryAndAssert(element, 'span#output')
- .childNodes[2] as HTMLAnchorElement;
-
- assert.equal(textNode.textContent, prefix);
-
- assert.isFalse(changeLinkEl.hasAttribute('target'));
- assert.isTrue(changeLinkEl.href.endsWith(changeUrl));
- assert.equal(changeLinkEl.textContent, changeID);
-
- assert.equal(bugLinkEl.target, '_blank');
- assert.equal(bugLinkEl.href, bugUrl);
- assert.equal(bugLinkEl.textContent, 'Issue 3650');
- });
-
- test('html field in link config', async () => {
- element.content = 'google:do a barrel roll';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.equal(
- linkEl.getAttribute('href'),
- 'https://google.com/search?q=do a barrel roll'
- );
- assert.equal(linkEl.textContent, 'do a barrel roll');
- });
-
- test('removing hash from links', async () => {
- element.content = 'hash:foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.isTrue(linkEl.href.endsWith('/awesomesauce'));
- assert.equal(linkEl.textContent, 'foo');
- });
-
- test('html with base url', async () => {
- window.CANONICAL_PATH = '/r';
-
- element.content = 'test foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
- assert.equal(linkEl.textContent, 'foo');
- });
-
- test('a is not at start', async () => {
- window.CANONICAL_PATH = '/r';
-
- element.content = 'a test foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[1] as HTMLAnchorElement;
- assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
- assert.equal(linkEl.textContent, 'foo');
- });
-
- test('hash html with base url', async () => {
- window.CANONICAL_PATH = '/r';
-
- element.content = 'hash:foo';
- await element.updateComplete;
-
- const linkEl = queryAndAssert(element, 'span#output')
- .childNodes[0] as HTMLAnchorElement;
- assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
- assert.equal(linkEl.textContent, 'foo');
- });
-
- test('disabled config', async () => {
- element.content = 'foo:baz';
- await element.updateComplete;
-
- assert.equal(queryAndAssert(element, 'span#output').innerHTML, 'foo:baz');
- });
-
- test('R=email labels link correctly', async () => {
- element.removeZeroWidthSpace = true;
- element.content = 'R=\u200Btest@google.com';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').textContent,
- 'R=test@google.com'
- );
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.match(/(R=<a)/g)!.length,
- 1
- );
- });
-
- test('CC=email labels link correctly', async () => {
- element.removeZeroWidthSpace = true;
- element.content = 'CC=\u200Btest@google.com';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').textContent,
- 'CC=test@google.com'
- );
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.match(/(CC=<a)/g)!
- .length,
- 1
- );
- });
-
- test('only {http,https,mailto} protocols are linkified', async () => {
- element.content = 'xx mailto:test@google.com yy';
- await element.updateComplete;
-
- let links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
- assert.equal(links[0].innerHTML, 'mailto:test@google.com');
-
- element.content = 'xx http://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'http://google.com');
- assert.equal(links[0].innerHTML, 'http://google.com');
-
- element.content = 'xx https://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'https://google.com');
- assert.equal(links[0].innerHTML, 'https://google.com');
-
- element.content = 'xx ssh://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 0);
-
- element.content = 'xx ftp://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 0);
- });
-
- test('links without leading whitespace are linkified', async () => {
- element.content = 'xx abcmailto:test@google.com yy';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
- 'xx abc'
- );
- let links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
- assert.equal(links[0].innerHTML, 'mailto:test@google.com');
-
- element.content = 'xx defhttp://google.com yy';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
- 'xx def'
- );
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'http://google.com');
- assert.equal(links[0].innerHTML, 'http://google.com');
-
- element.content = 'xx qwehttps://google.com yy';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
- 'xx qwe'
- );
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'https://google.com');
- assert.equal(links[0].innerHTML, 'https://google.com');
-
- // Non-latin character
- element.content = 'xx абвhttps://google.com yy';
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert(element, 'span#output').innerHTML.substr(0, 6),
- 'xx абв'
- );
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 1);
- assert.equal(links[0].getAttribute('href'), 'https://google.com');
- assert.equal(links[0].innerHTML, 'https://google.com');
-
- element.content = 'xx ssh://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 0);
-
- element.content = 'xx ftp://google.com yy';
- await element.updateComplete;
-
- links = queryAndAssert(element, 'span#output').querySelectorAll('a');
- assert.equal(links.length, 0);
- });
-
- test('overlapping links', async () => {
- element.config = {
- b1: {
- match: '(B:\\s*)(\\d+)',
- html: '$1<a href="ftp://foo/$2">$2</a>',
- },
- b2: {
- match: '(B:\\s*\\d+\\s*,\\s*)(\\d+)',
- html: '$1<a href="ftp://foo/$2">$2</a>',
- },
- };
- element.content = '- B: 123, 45';
- await element.updateComplete;
-
- const links = element.querySelectorAll('a');
-
- assert.equal(links.length, 2);
- assert.equal(
- queryAndAssert<HTMLSpanElement>(element, 'span').textContent,
- '- B: 123, 45'
- );
-
- assert.equal(links[0].href, 'ftp://foo/123');
- assert.equal(links[0].textContent, '123');
-
- assert.equal(links[1].href, 'ftp://foo/45');
- assert.equal(links[1].textContent, '45');
- });
-
- test('_contentOrConfigChanged called with config', async () => {
- const contentConfigStub = sinon.stub(element, '_contentOrConfigChanged');
- element.content = 'some text';
- await element.updateComplete;
-
- assert.isTrue(contentConfigStub.called);
- });
-});
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.ts
deleted file mode 100644
index 73cf58b..0000000
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.ts
+++ /dev/null
@@ -1,415 +0,0 @@
-/**
- * @license
- * Copyright 2015 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import 'ba-linkify/ba-linkify';
-import {getBaseUrl} from '../../../utils/url-util';
-import {CommentLinkInfo} from '../../../types/common';
-
-/**
- * Pattern describing URLs with supported protocols.
- */
-const URL_PROTOCOL_PATTERN = /^(.*)(https?:\/\/|mailto:)/;
-
-export type LinkTextParserCallback = ((text: string, href: string) => void) &
- ((text: null, href: null, fragment: DocumentFragment) => void);
-
-export interface CommentLinkItem {
- position: number;
- length: number;
- html: HTMLAnchorElement | DocumentFragment;
-}
-
-export type LinkTextParserConfig = {[name: string]: CommentLinkInfo};
-
-export class GrLinkTextParser {
- private readonly baseUrl = getBaseUrl();
-
- /**
- * Construct a parser for linkifying text. Will linkify plain URLs that appear
- * in the text as well as custom links if any are specified in the linkConfig
- * parameter.
- *
- * @param linkConfig Comment links as specified by the commentlinks field on a
- * project config.
- * @param callback The callback to be fired when an intermediate parse result
- * is emitted. The callback is passed text and href strings if a link is to
- * be created, or a document fragment otherwise.
- * @param removeZeroWidthSpace If true, zero-width spaces will be removed from
- * R=<email> and CC=<email> expressions.
- */
- constructor(
- private readonly linkConfig: LinkTextParserConfig,
- private readonly callback: LinkTextParserCallback,
- private readonly removeZeroWidthSpace?: boolean
- ) {
- Object.preventExtensions(this);
- }
-
- /**
- * Emit a callback to create a link element.
- *
- * @param text The text of the link.
- * @param href The URL to use as the href of the link.
- */
- addText(text: string, href: string) {
- if (!text) {
- return;
- }
- this.callback(text, href);
- }
-
- /**
- * Given the source text and a list of CommentLinkItem objects that were
- * generated by the commentlinks config, emit parsing callbacks.
- *
- * @param text The chuml of source text over which the outputArray items range.
- * @param outputArray The list of items to add resulting from commentlink
- * matches.
- */
- processLinks(text: string, outputArray: CommentLinkItem[]) {
- this.sortArrayReverse(outputArray);
- const fragment = document.createDocumentFragment();
- let cursor = text.length;
-
- // Start inserting linkified URLs from the end of the String. That way, the
- // string positions of the items don't change as we iterate through.
- outputArray.forEach(item => {
- // Add any text between the current linkified item and the item added
- // before if it exists.
- if (item.position + item.length !== cursor) {
- fragment.insertBefore(
- document.createTextNode(
- text.slice(item.position + item.length, cursor)
- ),
- fragment.firstChild
- );
- }
- fragment.insertBefore(item.html, fragment.firstChild);
- cursor = item.position;
- });
-
- // Add the beginning portion at the end.
- if (cursor !== 0) {
- fragment.insertBefore(
- document.createTextNode(text.slice(0, cursor)),
- fragment.firstChild
- );
- }
-
- this.callback(null, null, fragment);
- }
-
- /**
- * Sort the given array of CommentLinkItems such that the positions are in
- * reverse order.
- */
- sortArrayReverse(outputArray: CommentLinkItem[]) {
- outputArray.sort((a, b) => b.position - a.position);
- }
-
- addItem(
- text: string,
- href: string,
- html: null,
- position: number,
- length: number,
- outputArray: CommentLinkItem[]
- ): void;
-
- addItem(
- text: null,
- href: null,
- html: string,
- position: number,
- length: number,
- outputArray: CommentLinkItem[]
- ): void;
-
- /**
- * Create a CommentLinkItem and append it to the given output array. This
- * method can be called in either of two ways:
- * - With `text` and `href` parameters provided, and the `html` parameter
- * passed as `null`. In this case, the new CommentLinkItem will be a link
- * element with the given text and href value.
- * - With the `html` paremeter provided, and the `text` and `href` parameters
- * passed as `null`. In this case, the string of HTML will be parsed and the
- * first resulting node will be used as the resulting content.
- *
- * @param text The text to use if creating a link.
- * @param href The href to use as the URL if creating a link.
- * @param html The html to parse and use as the result.
- * @param position The position inside the source text where the item
- * starts.
- * @param length The number of characters in the source text
- * represented by the item.
- * @param outputArray The array to which the
- * new item is to be appended.
- */
- addItem(
- text: string | null,
- href: string | null,
- html: string | null,
- position: number,
- length: number,
- outputArray: CommentLinkItem[]
- ): void {
- if (href) {
- const a = document.createElement('a');
- a.setAttribute('href', href);
- a.textContent = text;
- a.target = '_blank';
- a.rel = 'noopener';
- outputArray.push({
- html: a,
- position,
- length,
- });
- } else if (html) {
- // addItem has 2 overloads. If href is null, then html
- // can't be null.
- // TODO(TS): remove if(html) and keep else block without condition
- const fragment = document.createDocumentFragment();
- // Create temporary div to hold the nodes in.
- const div = document.createElement('div');
- div.innerHTML = html;
- while (div.firstChild) {
- fragment.appendChild(div.firstChild);
- }
- outputArray.push({
- html: fragment,
- position,
- length,
- });
- }
- }
-
- /**
- * Create a CommentLinkItem for a link and append it to the given output
- * array.
- *
- * @param text The text for the link.
- * @param href The href to use as the URL of the link.
- * @param position The position inside the source text where the link
- * starts.
- * @param length The number of characters in the source text
- * represented by the link.
- * @param outputArray The array to which the
- * new item is to be appended.
- */
- addLink(
- text: string,
- href: string,
- position: number,
- length: number,
- outputArray: CommentLinkItem[]
- ) {
- // TODO(TS): remove !test condition
- if (!text || this.hasOverlap(position, length, outputArray)) {
- return;
- }
- if (
- !!this.baseUrl &&
- href.startsWith('/') &&
- !href.startsWith(this.baseUrl)
- ) {
- href = this.baseUrl + href;
- }
- this.addItem(text, href, null, position, length, outputArray);
- }
-
- /**
- * Create a CommentLinkItem specified by an HTMl string and append it to the
- * given output array.
- *
- * @param html The html to parse and use as the result.
- * @param position The position inside the source text where the item
- * starts.
- * @param length The number of characters in the source text
- * represented by the item.
- * @param outputArray The array to which the
- * new item is to be appended.
- */
- addHTML(
- html: string,
- position: number,
- length: number,
- outputArray: CommentLinkItem[]
- ) {
- if (this.hasOverlap(position, length, outputArray)) {
- return;
- }
- if (
- !!this.baseUrl &&
- html.match(/<a href="\//g) &&
- !new RegExp(`<a href="${this.baseUrl}`, 'g').test(html)
- ) {
- html = html.replace(/<a href="\//g, `<a href="${this.baseUrl}/`);
- }
- this.addItem(null, null, html, position, length, outputArray);
- }
-
- /**
- * Does the given range overlap with anything already in the item list.
- */
- hasOverlap(position: number, length: number, outputArray: CommentLinkItem[]) {
- const endPosition = position + length;
- for (let i = 0; i < outputArray.length; i++) {
- const arrayItemStart = outputArray[i].position;
- const arrayItemEnd = outputArray[i].position + outputArray[i].length;
- if (
- (position >= arrayItemStart && position < arrayItemEnd) ||
- (endPosition > arrayItemStart && endPosition <= arrayItemEnd) ||
- (position === arrayItemStart && position === arrayItemEnd)
- ) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Parse the given source text and emit callbacks for the items that are
- * parsed.
- */
- parse(text?: string | null) {
- if (text) {
- window.linkify(text, {
- callback: (text: string, href?: string) => this.parseChunk(text, href),
- });
- }
- }
-
- /**
- * Callback that is pased into the linkify function. ba-linkify will call this
- * method in either of two ways:
- * - With both a `text` and `href` parameter provided: this indicates that
- * ba-linkify has found a plain URL and wants it linkified.
- * - With only a `text` parameter provided: this represents the non-link
- * content that lies between the links the library has found.
- *
- */
- parseChunk(text: string, href?: string) {
- // TODO(wyatta) switch linkify sequence, see issue 5526.
- if (this.removeZeroWidthSpace) {
- // Remove the zero-width space added in gr-change-view.
- text = text.replace(/^(CC|R)=\u200B/gm, '$1=');
- }
-
- // If the href is provided then ba-linkify has recognized it as a URL. If
- // the source text does not include a protocol, the protocol will be added
- // by ba-linkify. Create the link if the href is provided and its protocol
- // matches the expected pattern.
- if (href) {
- const result = URL_PROTOCOL_PATTERN.exec(href);
- if (result) {
- const prefixText = result[1];
- if (prefixText.length > 0) {
- // Fix for simple cases from
- // https://bugs.chromium.org/p/gerrit/issues/detail?id=11697
- // When leading whitespace is missed before link,
- // linkify add this text before link as a schema name to href.
- // We suppose, that prefixText just a single word
- // before link and add this word as is, without processing
- // any patterns in it.
- this.parseLinks(prefixText, {});
- text = text.substring(prefixText.length);
- href = href.substring(prefixText.length);
- }
- this.addText(text, href);
- return;
- }
- }
- // For the sections of text that lie between the links found by
- // ba-linkify, we search for the project-config-specified link patterns.
- this.parseLinks(text, this.linkConfig);
- }
-
- /**
- * Walk over the given source text to find matches for comemntlink patterns
- * and emit parse result callbacks.
- *
- * @param text The raw source text.
- * @param config A comment links specification object.
- */
- parseLinks(text: string, config: LinkTextParserConfig) {
- // The outputArray is used to store all of the matches found for all
- // patterns.
- const outputArray: CommentLinkItem[] = [];
- for (const [configName, linkInfo] of Object.entries(config)) {
- // TODO(TS): it seems, the following line can be rewritten as:
- // if(enabled === false || enabled === 0 || enabled === '')
- // Should be double-checked before update
- // eslint-disable-next-line eqeqeq
- if (linkInfo.enabled != null && linkInfo.enabled == false) {
- continue;
- }
- // PolyGerrit doesn't use hash-based navigation like the GWT UI.
- // Account for this.
- const html = linkInfo.html;
- const link = linkInfo.link;
- if (html) {
- linkInfo.html = html.replace(/<a href="#\//g, '<a href="/');
- } else if (link) {
- if (link[0] === '#') {
- linkInfo.link = link.substr(1);
- }
- }
-
- const pattern = new RegExp(linkInfo.match, 'g');
-
- let match;
- let textToCheck = text;
- let susbtrIndex = 0;
-
- while ((match = pattern.exec(textToCheck))) {
- textToCheck = textToCheck.substr(match.index + match[0].length);
- let result = match[0].replace(
- pattern,
- // Either html or link has a value. Otherwise an exception is thrown
- // in the code below.
- (linkInfo.html || linkInfo.link)!
- );
-
- if (linkInfo.html) {
- let i;
- // Skip portion of replacement string that is equal to original to
- // allow overlapping patterns.
- for (i = 0; i < result.length; i++) {
- if (result[i] !== match[0][i]) {
- break;
- }
- }
- result = result.slice(i);
-
- this.addHTML(
- result,
- susbtrIndex + match.index + i,
- match[0].length - i,
- outputArray
- );
- } else if (linkInfo.link) {
- this.addLink(
- match[0],
- result,
- susbtrIndex + match.index,
- match[0].length,
- outputArray
- );
- } else {
- throw Error(
- 'linkconfig entry ' +
- configName +
- ' doesn’t contain a link or html attribute.'
- );
- }
-
- // Update the substring location so we know where we are in relation to
- // the initial full text string.
- susbtrIndex = susbtrIndex + match.index + match[0].length;
- }
- }
- this.processLinks(text, outputArray);
- }
-}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 7b5ce15..9a114e6 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -260,8 +260,6 @@
.suggestions=${this.suggestions}
.horizontalOffset=${20}
.verticalOffset=${20}
- vertical-align="top"
- horizontal-align="left"
@dropdown-closed=${this.resetDropdown}
@item-selected=${this.handleDropdownItemSelect}
>
@@ -275,8 +273,6 @@
return html` <gr-autocomplete-dropdown
id="mentionsSuggestions"
.suggestions=${this.suggestions}
- vertical-align="top"
- horizontal-align="left"
@dropdown-closed=${this.resetDropdown}
@item-selected=${this.handleDropdownItemSelect}
.horizontalOffset=${20}
@@ -525,7 +521,7 @@
// Otherwise open the dropdown and set the position to be just below the
// cursor.
// Do not open dropdown if textarea is not focused
- activeDropdown!.positionTarget = this.updateCaratPosition();
+ activeDropdown.setPositionTarget(this.updateCaratPosition());
activate();
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
index 6837a71..78c8aa3 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -33,8 +33,6 @@
<span id="caratSpan"> </span>
<gr-autocomplete-dropdown
id="emojiSuggestions"
- horizontal-align="left"
- vertical-align="top"
is-hidden=""
style="position: fixed; top: 150px; left: 392.5px; box-sizing: border-box; max-height: 300px; max-width: 785px;"
>
@@ -64,20 +62,16 @@
<div id="hiddenText"></div>
<span id="caratSpan"> </span>
<gr-autocomplete-dropdown
- horizontal-align="left"
id="emojiSuggestions"
is-hidden=""
style="position: fixed; top: 478px; left: 321px; box-sizing: border-box; max-height: 956px; max-width: 642px;"
- vertical-align="top"
>
</gr-autocomplete-dropdown>
<gr-autocomplete-dropdown
- horizontal-align="left"
id="mentionsSuggestions"
is-hidden=""
role="listbox"
style="position: fixed; top: 478px; left: 321px; box-sizing: border-box; max-height: 956px; max-width: 642px;"
- vertical-align="top"
>
</gr-autocomplete-dropdown>
<iron-autogrow-textarea
@@ -581,6 +575,7 @@
element.textarea!.selectionStart = 1;
element.textarea!.selectionEnd = 2;
element.text = ':1';
+ await element.emojiSuggestions!.updateComplete;
await element.updateComplete;
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index fe4632b..8a92bcc 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -13,6 +13,7 @@
import '@polymer/paper-listbox/paper-listbox';
import './gr-overview-image';
import './gr-zoomed-image';
+import '../../../elements/shared/gr-icons/gr-icons';
import {GrLibLoader} from '../../../elements/shared/gr-lib-loader/gr-lib-loader';
import {RESEMBLEJS_LIBRARY_CONFIG} from '../../../elements/shared/gr-lib-loader/resemblejs_config';
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index f0697df..53c2780 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -5,7 +5,6 @@
*/
import '../../../styles/shared-styles';
import '../../../elements/shared/gr-button/gr-button';
-import '../../../elements/shared/gr-icons/gr-icons';
import '../../../elements/shared/gr-icon/gr-icon';
import '../gr-diff-builder/gr-diff-builder-element';
import '../gr-diff-highlight/gr-diff-highlight';
diff --git a/polygerrit-ui/app/models/checks/checks-util.ts b/polygerrit-ui/app/models/checks/checks-util.ts
index 09c9273..7ccdf91 100644
--- a/polygerrit-ui/app/models/checks/checks-util.ts
+++ b/polygerrit-ui/app/models/checks/checks-util.ts
@@ -8,12 +8,16 @@
Category,
CheckResult as CheckResultApi,
CheckRun as CheckRunApi,
+ Fix,
Link,
LinkIcon,
+ Replacement,
RunStatus,
} from '../../api/checks';
import {PatchSetNumber} from '../../api/rest-api';
+import {FixSuggestionInfo, FixReplacementInfo} from '../../types/common';
import {OpenFixPreviewEventDetail} from '../../types/events';
+import {notUndefined} from '../../types/types';
import {PROVIDED_FIX_ID} from '../../utils/comment-util';
import {assert, assertNever} from '../../utils/common-util';
import {fire} from '../../utils/event-util';
@@ -86,17 +90,15 @@
target: EventTarget,
result?: RunResult
): Action | undefined {
- const fixes = result?.fixes;
- if (!fixes || fixes?.length === 0 || !result?.patchset) return;
+ if (!result?.patchset) return;
+ if (!result?.fixes) return;
+ const fixSuggestions = result.fixes
+ .map(f => rectifyFix(f, result?.checkName))
+ .filter(notUndefined);
+ if (fixSuggestions.length === 0) return;
const eventDetail: OpenFixPreviewEventDetail = {
- patchNum: result?.patchset as PatchSetNumber,
- fixSuggestions: fixes.map(fix => {
- return {
- description: `Fix provided by ${result?.checkName}`,
- fix_id: PROVIDED_FIX_ID,
- ...fix,
- };
- }),
+ patchNum: result.patchset as PatchSetNumber,
+ fixSuggestions,
};
return {
name: 'Show Fix',
@@ -107,6 +109,36 @@
};
}
+export function rectifyFix(
+ fix: Fix | undefined,
+ checkName: string
+): FixSuggestionInfo | undefined {
+ if (!fix?.replacements) return undefined;
+ const replacements = fix.replacements
+ .map(rectifyReplacement)
+ .filter(notUndefined);
+ if (replacements.length === 0) return undefined;
+
+ return {
+ description: fix.description ?? `Fix provided by ${checkName}`,
+ fix_id: PROVIDED_FIX_ID,
+ replacements,
+ };
+}
+
+export function rectifyReplacement(
+ r: Replacement | undefined
+): FixReplacementInfo | undefined {
+ if (!r?.path) return undefined;
+ if (!r?.range) return undefined;
+ if (r?.replacement === undefined) return undefined;
+ if (!Number.isInteger(r.range.start_line)) return undefined;
+ if (!Number.isInteger(r.range.end_line)) return undefined;
+ if (!Number.isInteger(r.range.start_character)) return undefined;
+ if (!Number.isInteger(r.range.end_character)) return undefined;
+ return r;
+}
+
export function worstCategory(run: CheckRun) {
if (hasResultsOf(run, Category.ERROR)) return Category.ERROR;
if (hasResultsOf(run, Category.WARNING)) return Category.WARNING;
diff --git a/polygerrit-ui/app/models/checks/checks-util_test.ts b/polygerrit-ui/app/models/checks/checks-util_test.ts
index 5a7bd75..c237c59 100644
--- a/polygerrit-ui/app/models/checks/checks-util_test.ts
+++ b/polygerrit-ui/app/models/checks/checks-util_test.ts
@@ -10,9 +10,13 @@
ALL_ATTEMPTS,
AttemptChoice,
LATEST_ATTEMPT,
+ rectifyFix,
sortAttemptChoices,
stringToAttemptChoice,
} from './checks-util';
+import {Fix, Replacement} from '../../api/checks';
+import {CommentRange} from '../../api/core';
+import {PROVIDED_FIX_ID} from '../../utils/comment-util';
suite('checks-util tests', () => {
setup(() => {});
@@ -33,6 +37,55 @@
assert.equal(stringToAttemptChoice('1x'), undefined);
});
+ test('rectifyFix', () => {
+ assert.isUndefined(rectifyFix(undefined, 'name'));
+ assert.isUndefined(rectifyFix({} as Fix, 'name'));
+ assert.isUndefined(
+ rectifyFix({description: 'asdf', replacements: []}, 'name')
+ );
+ assert.isUndefined(
+ rectifyFix(
+ {description: 'asdf', replacements: [{} as Replacement]},
+ 'test-check-name'
+ )
+ );
+ assert.isUndefined(
+ rectifyFix(
+ {
+ description: 'asdf',
+ replacements: [
+ {
+ path: 'test-path',
+ range: {} as CommentRange,
+ replacement: 'test-replacement-string',
+ },
+ ],
+ },
+ 'test-check-name'
+ )
+ );
+ const rectified = rectifyFix(
+ {
+ replacements: [
+ {
+ path: 'test-path',
+ range: {
+ start_line: 1,
+ end_line: 1,
+ start_character: 0,
+ end_character: 1,
+ } as CommentRange,
+ replacement: 'test-replacement-string',
+ },
+ ],
+ },
+ 'test-check-name'
+ );
+ assert.isDefined(rectified);
+ assert.equal(rectified?.description, 'Fix provided by test-check-name');
+ assert.equal(rectified?.fix_id, PROVIDED_FIX_ID);
+ });
+
test('sortAttemptChoices', () => {
const unsorted: (AttemptChoice | undefined)[] = [
3,
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 826ec66..100c46b 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -11,8 +11,10 @@
ChangeInfo,
PatchSetNumber,
} from '../../api/rest-api';
+import {Tab} from '../../constants/constants';
import {GerritView} from '../../services/router/router-model';
import {UrlEncodedCommentId} from '../../types/common';
+import {toggleSet} from '../../utils/common-util';
import {select} from '../../utils/observable-util';
import {
encodeURL,
@@ -33,7 +35,8 @@
patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
commentId?: UrlEncodedCommentId;
- tab?: string;
+ /** This can be a string only for plugin provided tabs. */
+ tab?: Tab | string;
/** Checks related view state */
@@ -43,6 +46,10 @@
filter?: string;
/** selected attempt for check runs (undefined=latest) */
attempt?: AttemptChoice;
+ /** selected check runs identified by `checkName` */
+ checksRunsSelected?: Set<string>;
+ /** regular expression for filtering check results */
+ checksResultsFilter?: string;
/** State properties that trigger one-time actions */
@@ -107,6 +114,15 @@
if (state.filter) {
queries.push(`filter=${state.filter}`);
}
+ if (state.checksResultsFilter) {
+ queries.push(`checksResultsFilter=${state.checksResultsFilter}`);
+ }
+ if (state.checksRunsSelected && state.checksRunsSelected.size > 0) {
+ queries.push(`checksRunsSelected=${[...state.checksRunsSelected].sort()}`);
+ }
+ if (state.tab && state.tab !== Tab.FILES) {
+ queries.push(`tab=${state.tab}`);
+ }
if (state.forceReload) {
queries.push('forceReload=true');
}
@@ -130,9 +146,9 @@
}
if (state.project) {
const encodedProject = encodeURL(state.project, true);
- return getBaseUrl() + `/c/${encodedProject}/+/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
} else {
- return getBaseUrl() + `/c/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
}
}
@@ -151,6 +167,16 @@
public readonly filter$ = select(this.state$, state => state?.filter);
+ public readonly checksResultsFilter$ = select(
+ this.state$,
+ state => state?.checksResultsFilter ?? ''
+ );
+
+ public readonly checksRunsSelected$ = select(
+ this.state$,
+ state => state?.checksRunsSelected ?? new Set<string>()
+ );
+
constructor() {
super(undefined);
this.state$.subscribe(s => {
@@ -163,4 +189,11 @@
}
});
}
+
+ toggleSelectedCheckRun(checkName: string) {
+ const current = this.getState()?.checksRunsSelected ?? new Set();
+ const next = new Set(current);
+ toggleSet(next, checkName);
+ this.updateState({checksRunsSelected: next});
+ }
}
diff --git a/polygerrit-ui/app/models/views/change_test.ts b/polygerrit-ui/app/models/views/change_test.ts
index 2f05e9f..24ced82 100644
--- a/polygerrit-ui/app/models/views/change_test.ts
+++ b/polygerrit-ui/app/models/views/change_test.ts
@@ -14,13 +14,15 @@
import '../../test/common-test-setup';
import {createChangeUrl, ChangeViewState} from './change';
+const STATE: ChangeViewState = {
+ view: GerritView.CHANGE,
+ changeNum: 1234 as NumericChangeId,
+ project: 'test' as RepoName,
+};
+
suite('change view state tests', () => {
test('createChangeUrl()', () => {
- const state: ChangeViewState = {
- view: GerritView.CHANGE,
- changeNum: 1234 as NumericChangeId,
- project: 'test' as RepoName,
- };
+ const state: ChangeViewState = {...STATE};
assert.equal(createChangeUrl(state), '/c/test/+/1234');
@@ -34,6 +36,37 @@
assert.equal(createChangeUrl(state), '/c/test/+/1234/5..10#123');
});
+ test('createChangeUrl() baseUrl', () => {
+ window.CANONICAL_PATH = '/base';
+ const state: ChangeViewState = {...STATE};
+ assert.equal(createChangeUrl(state).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
+ });
+
+ test('createChangeUrl() checksRunsSelected', () => {
+ const state: ChangeViewState = {
+ ...STATE,
+ checksRunsSelected: new Set(['asdf']),
+ };
+
+ assert.equal(
+ createChangeUrl(state),
+ '/c/test/+/1234?checksRunsSelected=asdf'
+ );
+ });
+
+ test('createChangeUrl() checksResultsFilter', () => {
+ const state: ChangeViewState = {
+ ...STATE,
+ checksResultsFilter: 'asdf.*qwer',
+ };
+
+ assert.equal(
+ createChangeUrl(state),
+ '/c/test/+/1234?checksResultsFilter=asdf.*qwer'
+ );
+ });
+
test('createChangeUrl() with repo name encoding', () => {
const state: ChangeViewState = {
view: GerritView.CHANGE,
diff --git a/polygerrit-ui/app/models/views/dashboard.ts b/polygerrit-ui/app/models/views/dashboard.ts
index 7141637..d9ff2d2 100644
--- a/polygerrit-ui/app/models/views/dashboard.ts
+++ b/polygerrit-ui/app/models/views/dashboard.ts
@@ -7,7 +7,7 @@
import {GerritView} from '../../services/router/router-model';
import {DashboardId} from '../../types/common';
import {DashboardSection} from '../../utils/dashboard-util';
-import {encodeURL} from '../../utils/url-util';
+import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -46,14 +46,14 @@
queryParams.push('title=' + encodeURIComponent(state.title));
}
const user = state.user ? state.user : '';
- return `/dashboard/${user}?${queryParams.join('&')}`;
+ return `${getBaseUrl()}/dashboard/${user}?${queryParams.join('&')}`;
} else if (repoName) {
// Project dashboard.
const encodedRepo = encodeURL(repoName, true);
- return `/p/${encodedRepo}/+/dashboard/${state.dashboard}`;
+ return `${getBaseUrl()}/p/${encodedRepo}/+/dashboard/${state.dashboard}`;
} else {
// User dashboard.
- return `/dashboard/${state.user || 'self'}`;
+ return `${getBaseUrl()}/dashboard/${state.user || 'self'}`;
}
}
diff --git a/polygerrit-ui/app/models/views/dashboard_test.ts b/polygerrit-ui/app/models/views/dashboard_test.ts
index 9deed72..86bb5c0 100644
--- a/polygerrit-ui/app/models/views/dashboard_test.ts
+++ b/polygerrit-ui/app/models/views/dashboard_test.ts
@@ -15,6 +15,12 @@
assert.equal(createDashboardUrl({}), '/dashboard/self');
});
+ test('baseUrl', () => {
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createDashboardUrl({}).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
+ });
+
test('user dashboard', () => {
assert.equal(createDashboardUrl({user: 'user'}), '/dashboard/user');
});
diff --git a/polygerrit-ui/app/models/views/diff.ts b/polygerrit-ui/app/models/views/diff.ts
index 63df521..3cc107a 100644
--- a/polygerrit-ui/app/models/views/diff.ts
+++ b/polygerrit-ui/app/models/views/diff.ts
@@ -12,7 +12,11 @@
} from '../../api/rest-api';
import {GerritView} from '../../services/router/router-model';
import {UrlEncodedCommentId} from '../../types/common';
-import {encodeURL, getPatchRangeExpression} from '../../utils/url-util';
+import {
+ encodeURL,
+ getBaseUrl,
+ getPatchRangeExpression,
+} from '../../utils/url-util';
import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -85,9 +89,9 @@
if (state.project) {
const encodedProject = encodeURL(state.project, true);
- return `/c/${encodedProject}/+/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
} else {
- return `/c/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
}
}
diff --git a/polygerrit-ui/app/models/views/diff_test.ts b/polygerrit-ui/app/models/views/diff_test.ts
index ec20037..b0f91bb 100644
--- a/polygerrit-ui/app/models/views/diff_test.ts
+++ b/polygerrit-ui/app/models/views/diff_test.ts
@@ -25,6 +25,10 @@
};
assert.equal(createDiffUrl(params), '/c/42/12/x%252By/path.cpp');
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createDiffUrl(params).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
+
params.project = 'test' as RepoName;
assert.equal(createDiffUrl(params), '/c/test/+/42/12/x%252By/path.cpp');
diff --git a/polygerrit-ui/app/models/views/edit.ts b/polygerrit-ui/app/models/views/edit.ts
index d8f4770..c63c8ce 100644
--- a/polygerrit-ui/app/models/views/edit.ts
+++ b/polygerrit-ui/app/models/views/edit.ts
@@ -10,7 +10,11 @@
RevisionPatchSetNum,
} from '../../api/rest-api';
import {GerritView} from '../../services/router/router-model';
-import {encodeURL, getPatchRangeExpression} from '../../utils/url-util';
+import {
+ encodeURL,
+ getBaseUrl,
+ getPatchRangeExpression,
+} from '../../utils/url-util';
import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -41,9 +45,9 @@
if (state.project) {
const encodedProject = encodeURL(state.project, true);
- return `/c/${encodedProject}/+/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
} else {
- return `/c/${state.changeNum}${suffix}`;
+ return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
}
}
diff --git a/polygerrit-ui/app/models/views/edit_test.ts b/polygerrit-ui/app/models/views/edit_test.ts
index e1a05c7..2912063 100644
--- a/polygerrit-ui/app/models/views/edit_test.ts
+++ b/polygerrit-ui/app/models/views/edit_test.ts
@@ -27,5 +27,9 @@
createEditUrl(params),
'/c/test-project/+/42/12/x%252By/path.cpp,edit#31'
);
+
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createEditUrl(params).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
});
});
diff --git a/polygerrit-ui/app/models/views/search.ts b/polygerrit-ui/app/models/views/search.ts
index 58cb8f7..13de8f3 100644
--- a/polygerrit-ui/app/models/views/search.ts
+++ b/polygerrit-ui/app/models/views/search.ts
@@ -6,7 +6,7 @@
import {RepoName, BranchName, TopicName} from '../../api/rest-api';
import {GerritView} from '../../services/router/router-model';
import {addQuotesWhen} from '../../utils/string-util';
-import {encodeURL} from '../../utils/url-util';
+import {encodeURL, getBaseUrl} from '../../utils/url-util';
import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
@@ -35,7 +35,7 @@
}
if (params.query) {
- return '/q/' + encodeURL(params.query, true) + offsetExpr;
+ return `${getBaseUrl()}/q/${encodeURL(params.query, true)}${offsetExpr}`;
}
const operators: string[] = [];
@@ -80,7 +80,7 @@
}
}
- return '/q/' + operators.join('+') + offsetExpr;
+ return `${getBaseUrl()}/q/${operators.join('+')}${offsetExpr}`;
}
export const searchViewModelToken =
diff --git a/polygerrit-ui/app/models/views/search_test.ts b/polygerrit-ui/app/models/views/search_test.ts
index 138ce1e..d48667b 100644
--- a/polygerrit-ui/app/models/views/search_test.ts
+++ b/polygerrit-ui/app/models/views/search_test.ts
@@ -23,6 +23,10 @@
'topic:g%2525h+status:op%2525en'
);
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createSearchUrl(options).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
+
options.offset = 100;
assert.equal(
createSearchUrl(options),
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 597bb6f..496513d 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -209,9 +209,7 @@
// Type for the custom event to switch tab.
export interface SwitchTabEventDetail {
// name of the tab to set as active, from custom event
- tab?: string;
- // index of tab to set as active, from paper-tabs event
- value?: number;
+ tab: string;
// scroll into the tab afterwards, from custom event
scrollIntoView?: boolean;
// define state of tab after opening
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index 05c054b..183d167 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -117,7 +117,7 @@
/**
* Add value, if the set does not contain it. Otherwise remove it.
*/
-export function toggleSetMembership<T>(set: Set<T>, value: T): void {
+export function toggleSet<T>(set: Set<T>, value: T): void {
if (set.has(value)) {
set.delete(value);
} else {
@@ -125,6 +125,14 @@
}
}
+export function toggle<T>(array: T[], item: T): T[] {
+ if (array.includes(item)) {
+ return array.filter(r => r !== item);
+ } else {
+ return array.concat([item]);
+ }
+}
+
export function unique<T>(item: T, index: number, array: T[]) {
return array.indexOf(item) === index;
}
diff --git a/polygerrit-ui/app/utils/common-util_test.ts b/polygerrit-ui/app/utils/common-util_test.ts
index 0d85f34..76c8a6c 100644
--- a/polygerrit-ui/app/utils/common-util_test.ts
+++ b/polygerrit-ui/app/utils/common-util_test.ts
@@ -11,6 +11,7 @@
containsAll,
intersection,
difference,
+ toggle,
} from './common-util';
suite('common-util tests', () => {
@@ -97,4 +98,11 @@
assert.deepEqual(difference([1, 2, 3], [1, 2, 3]), []);
assert.deepEqual(difference([1, 2, 3], [4, 5, 6]), [1, 2, 3]);
});
+
+ test('toggle', () => {
+ assert.deepEqual(toggle([], 1), [1]);
+ assert.deepEqual(toggle([1], 1), []);
+ assert.deepEqual(toggle([1, 2, 3], 1), [2, 3]);
+ assert.deepEqual(toggle([2, 3], 1), [2, 3, 1]);
+ });
});
diff --git a/polygerrit-ui/app/utils/link-util.ts b/polygerrit-ui/app/utils/link-util.ts
index 9079f4c..b5b9025 100644
--- a/polygerrit-ui/app/utils/link-util.ts
+++ b/polygerrit-ui/app/utils/link-util.ts
@@ -4,76 +4,164 @@
* SPDX-License-Identifier: Apache-2.0
*/
import 'ba-linkify/ba-linkify';
-import {CommentLinks} from '../types/common';
+import {CommentLinkInfo, CommentLinks} from '../types/common';
import {getBaseUrl} from './url-util';
-export function linkifyNormalUrls(base: string): string {
- // Some tools are known to look for reviewers/CCs by finding lines such as
- // "R=foo@gmail.com, bar@gmail.com". However, "=" is technically a valid email
- // character, so ba-linkify interprets the entire string "R=foo@gmail.com" as
- // an email address. To fix this, we insert a zero width space character
- // \u200B before linking that prevents ba-linkify from associating the prefix
- // with the email. After linking we remove the zero width space.
- const baseWithZeroWidthSpace = base.replace(/^(R=|CC=)/g, '$&\u200B');
+/**
+ * Finds links within the base string and convert them to HTML. Config-based
+ * rewrites are only applied on text that is not linked by the default linking
+ * library.
+ */
+export function linkifyUrlsAndApplyRewrite(
+ base: string,
+ repoCommentLinks: CommentLinks
+): string {
const parts: string[] = [];
- window.linkify(baseWithZeroWidthSpace, {
+ window.linkify(insertZeroWidthSpace(base), {
callback: (text, href) => {
- const result = href ? createLinkTemplate(href, text) : text;
- const resultWithoutZeroWidthSpace = result.replace(/\u200B/g, '');
- parts.push(resultWithoutZeroWidthSpace);
+ if (href) {
+ parts.push(removeZeroWidthSpace(createLinkTemplate(href, text)));
+ } else {
+ const rewriteResults = getRewriteResultsFromConfig(
+ text,
+ repoCommentLinks
+ );
+ parts.push(removeZeroWidthSpace(applyRewrites(text, rewriteResults)));
+ }
},
});
return parts.join('');
}
-export function applyLinkRewritesFromConfig(
+/**
+ * Generates a list of rewrites that would be applied to a base string. They are
+ * not applied immediately to the base text because one rewrite may interfere or
+ * overlap with a later rewrite. Only after all rewrites are known they are
+ * carefully merged with `applyRewrites`.
+ */
+function getRewriteResultsFromConfig(
base: string,
repoCommentLinks: CommentLinks
-) {
- const linkRewritesFromConfig = Object.values(repoCommentLinks).filter(
- commentLinkInfo => commentLinkInfo.enabled !== false && commentLinkInfo.link
+): RewriteResult[] {
+ const enabledRewrites = Object.values(repoCommentLinks).filter(
+ commentLinkInfo =>
+ commentLinkInfo.enabled !== false &&
+ (commentLinkInfo.link !== undefined || commentLinkInfo.html !== undefined)
);
- const rewrites = linkRewritesFromConfig.map(rewrite => {
- const replacementHref = rewrite.link!.startsWith('/')
- ? `${getBaseUrl()}${rewrite.link!}`
- : rewrite.link!;
- return {
- match: new RegExp(rewrite.match, 'g'),
- replace: createLinkTemplate(
+ return enabledRewrites.flatMap(rewrite => {
+ const regexp = new RegExp(rewrite.match, 'g');
+ const partialResults: RewriteResult[] = [];
+ let match: RegExpExecArray | null;
+
+ while ((match = regexp.exec(base)) !== null) {
+ const fullReplacementText = getReplacementText(match[0], rewrite);
+ // The replacement may not be changing the entire matched substring so we
+ // "trim" the replacement position and text to the part that is actually
+ // different. This makes sure that unchanged portions are still eligible
+ // for other rewrites without being rejected as overlaps during
+ // `applyRewrites`. The new `replacementText` is not eligible for other
+ // rewrites since it would introduce unexpected interactions between
+ // rewrites depending on their order of definition/execution.
+ const sharedPrefixLength = getSharedPrefixLength(
+ match[0],
+ fullReplacementText
+ );
+ const sharedSuffixLength = getSharedSuffixLength(
+ match[0],
+ fullReplacementText
+ );
+ const prefixIndex = sharedPrefixLength;
+ const matchSuffixIndex = match[0].length - sharedSuffixLength;
+ const fullReplacementSuffixIndex =
+ fullReplacementText.length - sharedSuffixLength;
+ partialResults.push({
+ replacedTextStartPosition: match.index + prefixIndex,
+ replacedTextEndPosition: match.index + matchSuffixIndex,
+ replacementText: fullReplacementText.substring(
+ prefixIndex,
+ fullReplacementSuffixIndex
+ ),
+ });
+ }
+ return partialResults;
+ });
+}
+
+/**
+ * Applies all the rewrites to the given base string. To resolve cases where
+ * multiple rewrites target overlapping pieces of the base string, the rewrite
+ * that ends latest is kept and the rest are not applied and discarded.
+ */
+function applyRewrites(base: string, rewriteResults: RewriteResult[]): string {
+ const rewritesByEndPosition = [...rewriteResults].sort((a, b) => {
+ if (b.replacedTextEndPosition !== a.replacedTextEndPosition) {
+ return b.replacedTextEndPosition - a.replacedTextEndPosition;
+ }
+ return a.replacedTextStartPosition - b.replacedTextStartPosition;
+ });
+ const filteredSortedRewrites: RewriteResult[] = [];
+ let latestReplace = base.length;
+ for (const rewrite of rewritesByEndPosition) {
+ // Only accept rewrites that do not overlap with any previously accepted
+ // rewrites.
+ if (rewrite.replacedTextEndPosition <= latestReplace) {
+ filteredSortedRewrites.push(rewrite);
+ latestReplace = rewrite.replacedTextStartPosition;
+ }
+ }
+ return filteredSortedRewrites.reduce(
+ (text, rewrite) =>
+ text
+ .substring(0, rewrite.replacedTextStartPosition)
+ .concat(rewrite.replacementText)
+ .concat(text.substring(rewrite.replacedTextEndPosition)),
+ base
+ );
+}
+
+/**
+ * For a given regexp match, apply the rewrite based on the rewrite's type and
+ * return the resulting string.
+ */
+function getReplacementText(
+ matchedText: string,
+ rewrite: CommentLinkInfo
+): string {
+ if (rewrite.link !== undefined) {
+ const replacementHref = rewrite.link.startsWith('/')
+ ? `${getBaseUrl()}${rewrite.link}`
+ : rewrite.link;
+ const regexp = new RegExp(rewrite.match, 'g');
+ return matchedText.replace(
+ regexp,
+ createLinkTemplate(
replacementHref,
rewrite.text ?? '$&',
rewrite.prefix,
rewrite.suffix
- ),
- };
- });
- return applyRewrites(base, rewrites);
+ )
+ );
+ } else if (rewrite.html !== undefined) {
+ return matchedText.replace(new RegExp(rewrite.match, 'g'), rewrite.html);
+ } else {
+ throw new Error('commentLinkInfo is not a link or html rewrite');
+ }
}
-export function applyHtmlRewritesFromConfig(
- base: string,
- repoCommentLinks: CommentLinks
-) {
- const htmlRewritesFromConfig = Object.values(repoCommentLinks).filter(
- commentLinkInfo => commentLinkInfo.enabled !== false && commentLinkInfo.html
- );
- const rewrites = htmlRewritesFromConfig.map(rewrite => {
- return {
- match: new RegExp(rewrite.match, 'g'),
- replace: rewrite.html!,
- };
- });
- return applyRewrites(base, rewrites);
+/**
+ * Some tools are known to look for reviewers/CCs by finding lines such as
+ * "R=foo@gmail.com, bar@gmail.com". However, "=" is technically a valid email
+ * character, so ba-linkify interprets the entire string "R=foo@gmail.com" as an
+ * email address. To fix this, we insert a zero width space character \u200B
+ * before linking that prevents ba-linkify from associating the prefix with the
+ * email. After linking we remove the zero width space.
+ */
+function insertZeroWidthSpace(base: string) {
+ return base.replace(/^(R=|CC=)/g, '$&\u200B');
}
-function applyRewrites(
- base: string,
- rewrites: {match: RegExp | string; replace: string}[]
-) {
- return rewrites.reduce(
- (text, rewrite) => text.replace(rewrite.match, rewrite.replace),
- base
- );
+function removeZeroWidthSpace(base: string) {
+ return base.replace(/\u200B/g, '');
}
function createLinkTemplate(
@@ -88,3 +176,41 @@
suffix ?? ''
}`;
}
+
+/**
+ * Returns the number of characters that are identical at the start of both
+ * strings.
+ *
+ * For example, `getSharedPrefixLength('12345678', '1234zz78')` would return 4
+ */
+function getSharedPrefixLength(a: string, b: string) {
+ let i = 0;
+ for (; i < a.length && i < b.length; ++i) {
+ if (a[i] !== b[i]) {
+ return i;
+ }
+ }
+ return i;
+}
+
+/**
+ * Returns the number of characters that are identical at the end of both
+ * strings.
+ *
+ * For example, `getSharedSuffixLength('12345678', '1234zz78')` would return 2
+ */
+function getSharedSuffixLength(a: string, b: string) {
+ let i = a.length;
+ for (let j = b.length; i !== 0 && j !== 0; --i, --j) {
+ if (a[i] !== b[j]) {
+ return a.length - 1 - i;
+ }
+ }
+ return a.length - i;
+}
+
+interface RewriteResult {
+ replacedTextStartPosition: number;
+ replacedTextEndPosition: number;
+ replacementText: string;
+}
diff --git a/polygerrit-ui/app/utils/link-util_test.ts b/polygerrit-ui/app/utils/link-util_test.ts
index a1ec2fa..61d6bff 100644
--- a/polygerrit-ui/app/utils/link-util_test.ts
+++ b/polygerrit-ui/app/utils/link-util_test.ts
@@ -3,11 +3,7 @@
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {
- applyHtmlRewritesFromConfig,
- applyLinkRewritesFromConfig,
- linkifyNormalUrls,
-} from './link-util';
+import {linkifyUrlsAndApplyRewrite} from './link-util';
import {assert} from '@open-wc/testing';
suite('link-util tests', () => {
@@ -15,55 +11,220 @@
return `<a href="${href}" rel="noopener" target="_blank">${text}</a>`;
}
- test('applyHtmlRewritesFromConfig', () => {
- assert.equal(
- applyHtmlRewritesFromConfig('#12345 foo', {
- 'number-emphasizer': {
- match: '#(\\d+)',
- html: '<h1>Change $1 is the best change</h1>',
- },
- 'foo-capitalizer': {
- match: 'foo',
- html: '<div>FOO</div>',
- },
- }),
- '<h1>Change 12345 is the best change</h1> <div>FOO</div>'
- );
+ suite('link rewrites', () => {
+ test('without text', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo', {
+ fooLinkWithoutText: {
+ match: 'foo',
+ link: 'foo.gov',
+ },
+ }),
+ link('foo', 'foo.gov')
+ );
+ });
+
+ test('with text', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo', {
+ fooLinkWithText: {
+ match: 'foo',
+ link: 'foo.gov',
+ text: 'foo site',
+ },
+ }),
+ link('foo site', 'foo.gov')
+ );
+ });
+
+ test('with prefix and suffix', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('there are 12 foos here', {
+ fooLinkWithText: {
+ match: '(.*)(bug|foo)s(.*)',
+ link: '$2.gov',
+ text: '$2 list',
+ prefix: '$1on the ',
+ suffix: '$3',
+ },
+ }),
+ `there are 12 on the ${link('foo list', 'foo.gov')} here`
+ );
+ });
+
+ test('multiple matches', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo foo', {
+ foo: {
+ match: 'foo',
+ link: 'foo.gov',
+ },
+ }),
+ `${link('foo', 'foo.gov')} ${link('foo', 'foo.gov')}`
+ );
+ });
+
+ test('does not apply within normal links', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('google.com', {
+ ogle: {
+ match: 'ogle',
+ link: 'gerritcodereview.com',
+ },
+ }),
+ link('google.com', 'http://google.com')
+ );
+ });
+ });
+ suite('html rewrites', () => {
+ test('basic case', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo', {
+ foo: {
+ match: '(foo)',
+ html: '<div>$1</div>',
+ },
+ }),
+ '<div>foo</div>'
+ );
+ });
+
+ test('only inserts', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo', {
+ foo: {
+ match: 'foo',
+ html: 'foo bar',
+ },
+ }),
+ 'foo bar'
+ );
+ });
+
+ test('only deletes', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo bar baz', {
+ bar: {
+ match: 'bar',
+ html: '',
+ },
+ }),
+ 'foo baz'
+ );
+ });
+
+ test('multiple matches', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foo foo', {
+ foo: {
+ match: '(foo)',
+ html: '<div>$1</div>',
+ },
+ }),
+ '<div>foo</div> <div>foo</div>'
+ );
+ });
+
+ test('does not apply within normal links', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('google.com', {
+ ogle: {
+ match: 'ogle',
+ html: '<div>gerritcodereview.com<div>',
+ },
+ }),
+ link('google.com', 'http://google.com')
+ );
+ });
});
- test('applyLinkRewritesFromConfig', () => {
- const linkedNumber = link('#12345', 'google.com/12345');
- const linkedFoo = link('foo', 'foo.gov');
- const linkedBar = link('Bar Page: 300', 'bar.com/page?id=300');
+ test('for overlapping rewrites prefer the latest ending', () => {
assert.equal(
- applyLinkRewritesFromConfig('#12345 foo crowbar:12 bar:300', {
- 'number-linker': {
- match: '#(\\d+)',
- link: 'google.com/$1',
- },
- 'foo-linker': {
+ linkifyUrlsAndApplyRewrite('foobarbaz', {
+ foo: {
match: 'foo',
link: 'foo.gov',
},
- 'advanced-link': {
- match: '(^|\\s)bar:(\\d+)($|\\s)',
- link: 'bar.com/page?id=$2',
- text: 'Bar Page: $2',
- prefix: '$1',
- suffix: '$3',
+ foobarbaz: {
+ match: 'foobarbaz',
+ html: '<div>foobarbaz.gov</div>',
+ },
+ foobar: {
+ match: 'foobar',
+ link: 'foobar.gov',
},
}),
- `${linkedNumber} ${linkedFoo} crowbar:12 ${linkedBar}`
+ '<div>foobarbaz.gov</div>'
);
});
- suite('linkifyNormalUrls', () => {
+ test('overlapping rewrites with same ending prefers earliest start', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foobarbaz', {
+ foo: {
+ match: 'baz',
+ link: 'Baz.gov',
+ },
+ foobarbaz: {
+ match: 'foobarbaz',
+ html: '<div>FooBarBaz.gov</div>',
+ },
+ foobar: {
+ match: 'barbaz',
+ link: 'BarBaz.gov',
+ },
+ }),
+ '<div>FooBarBaz.gov</div>'
+ );
+ });
+
+ test('removed overlapping rewrites do not prevent other rewrites', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('foobarbaz', {
+ foo: {
+ match: 'foo',
+ html: 'FOO',
+ },
+ oobarba: {
+ match: 'oobarba',
+ html: 'OOBARBA',
+ },
+ baz: {
+ match: 'baz',
+ html: 'BAZ',
+ },
+ }),
+ 'FOObarBAZ'
+ );
+ });
+
+ test('rewrites do not interfere with each other matching', () => {
+ assert.equal(
+ linkifyUrlsAndApplyRewrite('bugs: 123 234 345', {
+ bug1: {
+ match: '(bugs:) (\\d+)',
+ html: '$1 <div>bug/$2</div>',
+ },
+ bug2: {
+ match: '(bugs:) (\\d+) (\\d+)',
+ html: '$1 $2 <div>bug/$3</div>',
+ },
+ bug3: {
+ match: '(bugs:) (\\d+) (\\d+) (\\d+)',
+ html: '$1 $2 $3 <div>bug/$4</div>',
+ },
+ }),
+ 'bugs: <div>bug/123</div> <div>bug/234</div> <div>bug/345</div>'
+ );
+ });
+
+ suite('normal links', () => {
test('links urls', () => {
const googleLink = link('google.com', 'http://google.com');
const mapsLink = link('maps.google.com', 'http://maps.google.com');
assert.equal(
- linkifyNormalUrls('google.com, maps.google.com'),
+ linkifyUrlsAndApplyRewrite('google.com, maps.google.com', {}),
`${googleLink}, ${mapsLink}`
);
});
@@ -72,7 +233,7 @@
const fooEmail = link('foo@gmail.com', 'mailto:foo@gmail.com');
const barEmail = link('bar@gmail.com', 'mailto:bar@gmail.com');
assert.equal(
- linkifyNormalUrls('R=foo@gmail.com, bar@gmail.com'),
+ linkifyUrlsAndApplyRewrite('R=foo@gmail.com, bar@gmail.com', {}),
`R=${fooEmail}, ${barEmail}`
);
});
@@ -81,7 +242,7 @@
const fooEmail = link('foo@gmail.com', 'mailto:foo@gmail.com');
const barEmail = link('bar@gmail.com', 'mailto:bar@gmail.com');
assert.equal(
- linkifyNormalUrls('CC=foo@gmail.com, bar@gmail.com'),
+ linkifyUrlsAndApplyRewrite('CC=foo@gmail.com, bar@gmail.com', {}),
`CC=${fooEmail}, ${barEmail}`
);
});
@@ -92,7 +253,7 @@
'mailto:fooR=barCC=baz@gmail.com'
);
assert.equal(
- linkifyNormalUrls('fooR=barCC=baz@gmail.com'),
+ linkifyUrlsAndApplyRewrite('fooR=barCC=baz@gmail.com', {}),
fooBarBazEmail
);
});
diff --git a/polygerrit-ui/app/utils/string-util_test.ts b/polygerrit-ui/app/utils/string-util_test.ts
index 0a7ee27..c6c65b1 100644
--- a/polygerrit-ui/app/utils/string-util_test.ts
+++ b/polygerrit-ui/app/utils/string-util_test.ts
@@ -12,7 +12,7 @@
diffFilePaths,
} from './string-util';
-suite('formatter util tests', () => {
+suite('string-util tests', () => {
test('pluralize', () => {
const noun = 'comment';
assert.equal(pluralize(0, noun), '');
diff --git a/tools/BUILD b/tools/BUILD
index f2d887e..a785c1b 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -46,12 +46,13 @@
"-XepDisableWarningsInGeneratedCode",
# The XepDisableWarningsInGeneratedCode disables only warnings, but
# not errors. We should manually exclude all files generated by
- # AutoValue; such files always start AutoValue_..., $AutoValue_...
- # or $$AutoValue_...
+ # AutoValue; such files always start AutoValue_..., $AutoValue_...,
+ # $$AutoValue_... or AutoValueGson_...
# XepExcludedPaths is a regexp. If you need more paths - use | as
# separator.
- "-XepExcludedPaths:.*/\\\\$$?\\\\$$?AutoValue_.*\\.java",
+ "-XepExcludedPaths:.*/\\\\$$?\\\\$$?AutoValue(Gson)?_.*\\.java",
"-Xep:AlmostJavadoc:ERROR",
+ "-Xep:AlreadyChecked:ERROR",
"-Xep:AlwaysThrows:ERROR",
"-Xep:AmbiguousMethodReference:ERROR",
"-Xep:AnnotateFormatMethod:ERROR",
@@ -351,6 +352,7 @@
"-Xep:RestrictedApiChecker:ERROR",
"-Xep:RethrowReflectiveOperationExceptionAsLinkageError:ERROR",
"-Xep:ReturnFromVoid:ERROR",
+ "-Xep:ReturnMissingNullable:ERROR",
"-Xep:ReturnValueIgnored:ERROR",
"-Xep:RxReturnValueIgnored:ERROR",
"-Xep:SameNameButDifferent:ERROR",