Merge "AttentionSetIT: fix errorprone warnings"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 61565f8..c3237ed 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -365,6 +365,13 @@
bazel test --test_tag_filters=api,git //...
----
+To run the tests against a specific index backend (LUCENE, FAKE):
+----
+ bazel test --test_env=GERRIT_INDEX_TYPE=LUCENE //...
+----
+
+Elastic search is not currently supported in integration tests.
+
The following values are currently supported for the group name:
* annotation
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png b/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png
deleted file mode 100644
index 69a28ec..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-change-info-cannot-merge.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-change-info.png b/Documentation/images/gwt-user-review-ui-change-screen-change-info.png
deleted file mode 100644
index e92b49d..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-change-info.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png b/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png
deleted file mode 100644
index 097637e..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-commit-info-merge-commit.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png b/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png
deleted file mode 100644
index fe0c1d1..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-commit-info.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png b/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png
deleted file mode 100644
index ad30fe2..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-included-in-list.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-not-current.png b/Documentation/images/gwt-user-review-ui-change-screen-not-current.png
deleted file mode 100644
index 9a87c67..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-not-current.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-permalink.png b/Documentation/images/gwt-user-review-ui-change-screen-permalink.png
deleted file mode 100644
index a1aede9..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-permalink.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png b/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png
deleted file mode 100644
index 120b99c..0000000
--- a/Documentation/images/gwt-user-review-ui-change-screen-plugin-extensions.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png
deleted file mode 100644
index 2ecc47e..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-comment.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png b/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png
deleted file mode 100644
index 6f63f0e4..0000000
--- a/Documentation/images/gwt-user-review-ui-side-by-side-diff-screen-keyboard-shortcuts.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/user-review-ui-change-screen-plugin-extensions.png b/Documentation/images/user-review-ui-change-screen-plugin-extensions.png
new file mode 100644
index 0000000..5d6fee7
--- /dev/null
+++ b/Documentation/images/user-review-ui-change-screen-plugin-extensions.png
Binary files differ
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 0d8da89..813ff44 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -1077,6 +1077,38 @@
----
+[[immer]]
+immer
+
+* immer
+
+[[immer_license]]
+----
+MIT License
+
+Copyright (c) 2017 Michel Weststrate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----
+
+
[[isarray]]
isarray
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index ed1a336..11f9ff3 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -4036,6 +4036,38 @@
----
+[[immer]]
+immer
+
+* immer
+
+[[immer_license]]
+----
+MIT License
+
+Copyright (c) 2017 Michel Weststrate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----
+
+
[[isarray]]
isarray
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 367908e..6f5f7297 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -218,7 +218,7 @@
** [[plugin-actions]]Further actions may be available if plugins are installed.
+
-image::images/user-review-ui-change-screen-change-info-actions.png[width=600, link="images/user-review-ui-change-screen-change-info-actions.png"]
+image::images/user-review-ui-change-screen-change-info-actions.png[width=400, link="images/user-review-ui-change-screen-change-info-actions.png"]
- [[labels]]Labels & Votes:
+
@@ -259,7 +259,7 @@
set is currently viewed can be seen from the `Patch Sets` drop-down
panel in the change header.
-image::images/user-review-ui-change-screen-patch-sets.png[width=487, link="images/user-review-ui-change-screen-patch-sets.png"]
+image::images/user-review-ui-change-screen-patch-sets.png[width=300, link="images/user-review-ui-change-screen-patch-sets.png"]
[[download]]
@@ -458,7 +458,7 @@
comments; a summary comment is only added if the reply popup panel is
open when the quick approve button is clicked.
-image::images/user-review-ui-change-screen-quick-approve.png[width=800, link="images/gwt-user-review-ui-change-screen-quick-approve.png"]
+image::images/user-review-ui-change-screen-quick-approve.png[width=800, link="images/user-review-ui-change-screen-quick-approve.png"]
[[history]]
=== History
@@ -485,11 +485,11 @@
[[plugin-extensions]]
=== Plugin Extensions
-Gerrit plugins may extend the change screen; they can add buttons for
-additional actions to the change info block and display arbitrary UI
-controls below the change info block.
+Gerrit plugins may extend the change screen. Java plugins in the
+backend can add additional actions to the triple-dot menu block.
+Frontend plugins can change the UI controls in arbitrary ways.
-image::images/gwt-user-review-ui-change-screen-plugin-extensions.png[width=800, link="images/gwt-user-review-ui-change-screen-plugin-extensions.png"]
+image::images/user-review-ui-change-screen-plugin-extensions.png[width=300, link="images/user-review-ui-change-screen-plugin-extensions.png"]
[[side-by-side]]
== Side-by-Side Diff Screen
@@ -509,7 +509,7 @@
diff preference allows to control whether the files should be
automatically marked as reviewed when they are viewed.
-image::images/user-review-ui-side-by-side-diff-screen-reviewed.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-reviewed.png"]
+image::images/user-review-ui-side-by-side-diff-screen-reviewed.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-reviewed.png"]
[[patch-set-selection]]
In the header, on each side, the list of patch sets is shown. Clicking
@@ -616,7 +616,7 @@
File level comments are added by clicking the 'File' header at the top
of the file.
-image::images/user-review-ui-side-by-side-diff-screen-file-level-comment.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-file-level-comment.png"]
+image::images/user-review-ui-side-by-side-diff-screen-file-level-comment.png[width=400, link="images/user-review-ui-side-by-side-diff-screen-file-level-comment.png"]
[[diff-preferences]]
=== Diff Preferences
@@ -626,7 +626,7 @@
preferences. The diff preferences can be accessed by clicking on the
settings icon in the screen header.
-image::images/user-review-ui-side-by-side-diff-screen-preferences.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-preferences.png"]
+image::images/user-review-ui-side-by-side-diff-screen-preferences.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-preferences.png"]
The following diff preferences can be configured:
@@ -673,7 +673,7 @@
If many lines are skipped there are additional links to expand the
context by ten lines before and after the skipped block.
+
-image::images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png[width=800, link="images/gwt-user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png"]
+image::images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png"]
- [[syntax-highlighting]]`Syntax Highlighting`:
+
@@ -703,7 +703,7 @@
a popup that shows a list of available keyboard shortcuts.
-image::images/user-review-ui-change-screen-keyboard-shortcuts.png[width=800, link="images/gwt-user-review-ui-change-screen-keyboard-shortcuts.png"]
+image::images/user-review-ui-change-screen-keyboard-shortcuts.png[width=800, link="images/user-review-ui-change-screen-keyboard-shortcuts.png"]
In addition, Vim-like commands can be used to link:#key-navigation[
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index a2dc31f..a9779b1 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -702,6 +702,18 @@
to one of the fields in the
link:rest-api-changes.html#submit-record[SubmitRecord] REST API entity.
+`label:Code-Review=MAX`::
++
+Matches changes with label voted with the highest possible score.
+
+`label:Code-Review=MIN`::
++
+Matches changes with label voted with the lowest possible score.
+
+`label:Code-Review=ANY`::
++
+Matches changes with label voted with any score.
+
`label:Non-Author-Code-Review=need`::
+
Matches changes where the submit rules indicate that a label named
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 5ee1a08..fa62cd9 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -75,6 +75,7 @@
"//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
"//java/com/google/gerrit/gpg/testing:gpg-test-util",
"//java/com/google/gerrit/git/testing",
+ "//java/com/google/gerrit/index/testing",
]
PGM_DEPLOY_ENV = [
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 93c1237..085fef5 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -45,6 +45,8 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.FakeIndexModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init;
@@ -449,9 +451,29 @@
cfg.setString("gitweb", null, "cgi", "");
cfg.setString(
"accountPatchReviewDb", null, "url", JdbcAccountPatchReviewStore.TEST_IN_MEMORY_URL);
+
+ String configuredIndexBackend = cfg.getString("index", null, "type");
+ IndexType indexType;
+ if (configuredIndexBackend != null) {
+ // Explicitly configured index backend from gerrit.config trumps any other ways to configure
+ // index backends so that Reindex tests can be explicit about the backend they want to test
+ // against.
+ indexType = new IndexType(configuredIndexBackend);
+ } else {
+ // Allow configuring the index backend based on sys/env variables so that integration tests
+ // can be run against different index backends.
+ indexType = IndexType.fromEnvironment().orElse(new IndexType("fake"));
+ }
+ if (indexType.isLucene()) {
+ daemon.setIndexModule(
+ LuceneIndexModule.singleVersionAllLatest(0, ReplicaUtil.isReplica(baseConfig)));
+ } else {
+ daemon.setIndexModule(FakeIndexModule.latestVersion(false));
+ }
+ // Elastic search is not supported in integration tests yet.
+
daemon.setEnableHttpd(desc.httpd());
- daemon.setLuceneModule(
- LuceneIndexModule.singleVersionAllLatest(0, ReplicaUtil.isReplica(baseConfig)));
+ daemon.setInMemory(true);
daemon.setDatabaseForTesting(
ImmutableList.of(
new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
@@ -476,6 +498,8 @@
String[] additionalArgs)
throws Exception {
requireNonNull(site);
+ daemon.addAdditionalSysModuleForTesting(
+ new ReindexProjectsAtStartup.Module(), new ReindexGroupsAtStartup.Module());
ExecutorService daemonService = Executors.newSingleThreadExecutor();
String[] args =
Stream.concat(
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 079f306..91d032e 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -122,6 +122,8 @@
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -365,6 +367,19 @@
return LuceneIndexModule.latestVersion(false);
} else if (indexType.isElasticsearch()) {
return ElasticIndexModule.latestVersion(false);
+ } else if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m = clazz.getMethod("latestVersion", boolean.class);
+ return (Module) m.invoke(null, false);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/index/IndexType.java b/java/com/google/gerrit/index/IndexType.java
index cade439..0c3a76a 100644
--- a/java/com/google/gerrit/index/IndexType.java
+++ b/java/com/google/gerrit/index/IndexType.java
@@ -14,8 +14,12 @@
package com.google.gerrit.index;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
+import java.util.Optional;
/**
* Index types supported by the secondary index.
@@ -28,12 +32,42 @@
* allows to not break that case upon core implementation changes.
*/
public class IndexType {
+ public static final String SYS_PROP = "gerrit.index.type";
+ private static final String ENV_VAR = "GERRIT_INDEX_TYPE";
+
private static final String LUCENE = "lucene";
private static final String ELASTICSEARCH = "elasticsearch";
private static final String FAKE = "fake";
private final String type;
+ /**
+ * Returns the index type in case it was set by an environment variable. This is useful to run
+ * tests against a certain index backend.
+ */
+ public static Optional<IndexType> fromEnvironment() {
+ String value = System.getenv(ENV_VAR);
+ if (Strings.isNullOrEmpty(value)) {
+ value = System.getProperty(SYS_PROP);
+ }
+ if (Strings.isNullOrEmpty(value)) {
+ return Optional.empty();
+ }
+ value = value.toUpperCase().replace("-", "_");
+ IndexType type = new IndexType(value);
+ if (!Strings.isNullOrEmpty(System.getenv(ENV_VAR))) {
+ checkArgument(
+ type != null, "Invalid value for env variable %s: %s", ENV_VAR, System.getenv(ENV_VAR));
+ } else {
+ checkArgument(
+ type != null,
+ "Invalid value for system property %s: %s",
+ SYS_PROP,
+ System.getProperty(SYS_PROP));
+ }
+ return Optional.of(type);
+ }
+
public IndexType(@Nullable String type) {
this.type = type == null ? getDefault() : type.toLowerCase();
}
diff --git a/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java b/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java
new file mode 100644
index 0000000..75d8de2
--- /dev/null
+++ b/java/com/google/gerrit/index/testing/FakeIndexModuleOnInit.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.testing;
+
+import com.google.gerrit.index.testing.AbstractFakeIndex.FakeAccountIndex;
+import com.google.gerrit.index.testing.AbstractFakeIndex.FakeGroupIndex;
+import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.inject.AbstractModule;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+public class FakeIndexModuleOnInit extends AbstractModule {
+ @Override
+ protected void configure() {
+ install(
+ new FactoryModuleBuilder()
+ .implement(AccountIndex.class, FakeAccountIndex.class)
+ .build(AccountIndex.Factory.class));
+
+ install(
+ new FactoryModuleBuilder()
+ .implement(GroupIndex.class, FakeGroupIndex.class)
+ .build(GroupIndex.Factory.class));
+ }
+}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 29c5788..2b4cfef 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -87,6 +87,7 @@
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.group.PeriodicGroupIndexer;
+import com.google.gerrit.server.index.AbstractIndexModule;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.OnlineUpgrader;
import com.google.gerrit.server.index.VersionManager;
@@ -128,6 +129,8 @@
import com.google.inject.Provider;
import com.google.inject.Stage;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -207,7 +210,7 @@
private Injector httpdInjector;
private Path runFile;
private boolean inMemoryTest;
- private AbstractModule luceneModule;
+ private AbstractModule indexModule;
private Module emailModule;
private List<Module> testSysModules = new ArrayList<>();
private List<Module> testSshModules = new ArrayList<>();
@@ -336,9 +339,13 @@
}
@VisibleForTesting
- public void setLuceneModule(LuceneIndexModule m) {
- luceneModule = m;
- inMemoryTest = true;
+ public void setIndexModule(AbstractIndexModule m) {
+ indexModule = m;
+ }
+
+ @VisibleForTesting
+ public void setInMemory(boolean inMemory) {
+ this.inMemoryTest = inMemory;
}
@VisibleForTesting
@@ -523,8 +530,8 @@
}
private Module createIndexModule() {
- if (luceneModule != null) {
- return luceneModule;
+ if (indexModule != null) {
+ return indexModule;
}
if (indexType.isLucene()) {
return LuceneIndexModule.latestVersion(replica);
@@ -532,6 +539,20 @@
if (indexType.isElasticsearch()) {
return ElasticIndexModule.latestVersion(replica);
}
+ if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m = clazz.getMethod("latestVersion", boolean.class);
+ return (Module) m.invoke(null, replica);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
+ }
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index 3935268..6e99007 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -39,6 +39,8 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -154,6 +156,21 @@
} else if (indexType.isElasticsearch()) {
indexModule =
ElasticIndexModule.singleVersionWithExplicitVersions(versions, threads, replica);
+ } else if (indexType.isFake()) {
+ // Use Reflection so that we can omit the fake index binary in production code. Test code does
+ // compile the component in.
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
+ Method m =
+ clazz.getMethod(
+ "singleVersionWithExplicitVersions", Map.class, int.class, boolean.class);
+ indexModule = (Module) m.invoke(null, versions, threads, replica);
+ } catch (NoSuchMethodException
+ | ClassNotFoundException
+ | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("can't create index", e);
+ }
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 62ff66a..c4b0040 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -32,6 +32,7 @@
import com.google.gerrit.pgm.init.api.InstallPlugins;
import com.google.gerrit.pgm.init.api.LibraryDownload;
import com.google.gerrit.pgm.init.index.IndexManagerOnInit;
+import com.google.gerrit.pgm.init.index.IndexModuleOnInit;
import com.google.gerrit.pgm.init.index.elasticsearch.ElasticIndexModuleOnInit;
import com.google.gerrit.pgm.init.index.lucene.LuceneIndexModuleOnInit;
import com.google.gerrit.pgm.util.SiteProgram;
@@ -57,6 +58,7 @@
import com.google.inject.util.Providers;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -418,6 +420,19 @@
modules.add(new LuceneIndexModuleOnInit());
} else if (indexType.isElasticsearch()) {
modules.add(new ElasticIndexModuleOnInit());
+ } else if (indexType.isFake()) {
+ try {
+ Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModuleOnInit");
+ Module indexOnInitModule = (Module) clazz.getDeclaredConstructor().newInstance();
+ modules.add(indexOnInitModule);
+ } catch (InstantiationException
+ | IllegalAccessException
+ | ClassNotFoundException
+ | NoSuchMethodException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("unable to create fake index", e);
+ }
+ modules.add(new IndexModuleOnInit());
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 005aebb..454df66 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -659,7 +659,9 @@
// Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
private void processCommandsUnsafe(
Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
- logger.atFine().log("Calling user: %s", user.getLoggableName());
+ logger.atFine().log("Calling user: %s, commands: %d", user.getLoggableName(), commands.size());
+
+ // If the list of groups is large, the log entry may get dropped, so separate out.
logger.atFine().log("Groups: %s", lazy(() -> user.getEffectiveGroups().getKnownGroups()));
if (!projectState.getProject().getState().permitsWrite()) {
@@ -669,8 +671,6 @@
return;
}
- logger.atFine().log("Parsing %d commands", commands.size());
-
List<ReceiveCommand> magicCommands = new ArrayList<>();
List<ReceiveCommand> regularCommands = new ArrayList<>();
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 9c39c6e..810cd4d 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -43,11 +43,13 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.io.Files;
import com.google.common.primitives.Longs;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.Project;
@@ -72,6 +74,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.MagicLabelValue;
import com.google.gson.Gson;
import com.google.protobuf.MessageLite;
import java.sql.Timestamp;
@@ -602,16 +605,35 @@
for (PatchSetApproval a : cd.currentApprovals()) {
if (a.value() != 0 && !a.isLegacySubmit()) {
allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
+ LabelType labelType = cd.getLabelTypes().byLabel(a.labelId());
+ allApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, a.accountId()));
if (owners && cd.change().getOwner().equals(a.accountId())) {
allApprovals.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
+ allApprovals.addAll(
+ getMaxMinAnyLabels(
+ a.label(), a.value(), labelType, ChangeQueryBuilder.OWNER_ACCOUNT_ID));
}
distinctApprovals.add(formatLabel(a.label(), a.value()));
+ distinctApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, null));
}
}
allApprovals.addAll(distinctApprovals);
return allApprovals;
}
+ private static List<String> getMaxMinAnyLabels(
+ String label, short labelVal, LabelType labelType, @Nullable Account.Id accountId) {
+ List<String> labels = new ArrayList<>();
+ if (labelVal == labelType.getMaxPositive()) {
+ labels.add(formatLabel(label, MagicLabelValue.MAX.name(), accountId));
+ }
+ if (labelVal == labelType.getMaxNegative()) {
+ labels.add(formatLabel(label, MagicLabelValue.MIN.name(), accountId));
+ }
+ labels.add(formatLabel(label, MagicLabelValue.ANY.name(), accountId));
+ return labels;
+ }
+
public static Set<String> getAuthorParts(ChangeData cd) {
return SchemaUtil.getPersonParts(cd.getAuthor());
}
@@ -696,6 +718,17 @@
+ (accountId != null ? "," + formatAccount(accountId) : "");
}
+ public static String formatLabel(String label, String value) {
+ return formatLabel(label, value, null);
+ }
+
+ public static String formatLabel(String label, String value, @Nullable Account.Id accountId) {
+ return label.toLowerCase()
+ + "="
+ + value
+ + (accountId != null ? "," + formatAccount(accountId) : "");
+ }
+
private static String formatAccount(Account.Id accountId) {
if (ChangeQueryBuilder.OWNER_ACCOUNT_ID.equals(accountId)) {
return ChangeQueryBuilder.ARG_ID_OWNER;
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 6355674..879da4f 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -150,6 +150,9 @@
*/
static final Schema<ChangeData> V63 = schema(V62, false);
+ /** Added support for MIN/MAX/ANY for {@link ChangeField#LABEL} */
+ static final Schema<ChangeData> V64 = schema(V63, false);
+
/**
* Name of the change index to be used when contacting index backends or loading configurations.
*/
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 94b5442..131de74 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -973,7 +973,7 @@
int eq = name.indexOf('=');
if (args.getSchema().hasField(ChangeField.SUBMIT_RECORD) && eq > 0) {
String statusName = name.substring(eq + 1).toUpperCase();
- if (!isInt(statusName)) {
+ if (!isInt(statusName) && !MagicLabelValue.tryParse(statusName).isPresent()) {
SubmitRecord.Label.Status status =
Enums.getIfPresent(SubmitRecord.Label.Status.class, statusName).orNull();
if (status == null) {
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 38d1dbe..989b4bb 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -59,12 +60,12 @@
protected static class Parsed {
protected final String label;
protected final String test;
- protected final int expVal;
+ protected final int numericValue;
- protected Parsed(String label, String test, int expVal) {
+ protected Parsed(String label, String test, int numericValue) {
this.label = label;
this.test = test;
- this.expVal = expVal;
+ this.numericValue = numericValue;
}
}
@@ -83,6 +84,14 @@
protected static List<Predicate<ChangeData>> predicates(Args args) {
String v = args.value;
+
+ try {
+ MagicLabelVote mlv = MagicLabelVote.parseWithEquals(v);
+ return ImmutableList.of(new MagicLabelPredicate(args, mlv));
+ } catch (IllegalArgumentException e) {
+ // Try next format.
+ }
+
Parsed parsed = null;
try {
@@ -108,7 +117,7 @@
} else {
range =
RangeUtil.getRange(
- parsed.label, parsed.test, parsed.expVal, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
+ parsed.label, parsed.test, parsed.numericValue, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
}
String prefix = range.prefix;
int min = range.min;
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
new file mode 100644
index 0000000..e3c58e47
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelPredicate.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LabelTypes;
+import com.google.gerrit.entities.LabelValue;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.project.ProjectState;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class MagicLabelPredicate extends ChangeIndexPredicate {
+ protected final LabelPredicate.Args args;
+ private final MagicLabelVote magicLabelVote;
+
+ public MagicLabelPredicate(LabelPredicate.Args args, MagicLabelVote magicLabelVote) {
+ super(ChangeField.LABEL, magicLabelVote.formatLabel());
+ this.args = args;
+ this.magicLabelVote = magicLabelVote;
+ }
+
+ @Override
+ public boolean match(ChangeData changeData) {
+ Change change = changeData.change();
+ if (change == null) {
+ // The change has disappeared.
+ //
+ return false;
+ }
+
+ Optional<ProjectState> project = args.projectCache.get(change.getDest().project());
+ if (!project.isPresent()) {
+ // The project has disappeared.
+ //
+ return false;
+ }
+
+ LabelType labelType = type(project.get().getLabelTypes(), magicLabelVote.label());
+ if (labelType == null) {
+ return false; // Label is not defined by this project.
+ }
+
+ switch (magicLabelVote.value()) {
+ case ANY:
+ return matchAny(changeData, labelType);
+ case MIN:
+ return matchNumeric(changeData, magicLabelVote.label(), labelType.getMin().getValue());
+ case MAX:
+ return matchNumeric(changeData, magicLabelVote.label(), labelType.getMax().getValue());
+ }
+
+ throw new IllegalStateException("Unsupported magic label value: " + magicLabelVote.value());
+ }
+
+ private boolean matchAny(ChangeData changeData, LabelType labelType) {
+ List<Predicate<ChangeData>> predicates = new ArrayList<>();
+ for (LabelValue labelValue : labelType.getValues()) {
+ if (labelValue.getValue() != 0) {
+ predicates.add(numericPredicate(labelType.getName(), labelValue.getValue()));
+ }
+ }
+ return or(predicates).asMatchable().match(changeData);
+ }
+
+ private boolean matchNumeric(ChangeData changeData, String label, short value) {
+ return numericPredicate(label, value).match(changeData);
+ }
+
+ private EqualsLabelPredicate numericPredicate(String label, short value) {
+ return new EqualsLabelPredicate(args, label, value, /* account= */ null);
+ }
+
+ protected static LabelType type(LabelTypes types, String toFind) {
+ if (types.byLabel(toFind) != null) {
+ return types.byLabel(toFind);
+ }
+
+ for (LabelType lt : types.getLabelTypes()) {
+ if (toFind.equalsIgnoreCase(lt.getName())) {
+ return lt;
+ }
+ }
+ return null;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelValue.java b/java/com/google/gerrit/server/query/change/MagicLabelValue.java
new file mode 100644
index 0000000..c4bcbe3
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelValue.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import java.util.Optional;
+
+public enum MagicLabelValue {
+ ANY,
+ MIN,
+ MAX;
+
+ public static Optional<MagicLabelValue> tryParse(String value) {
+ try {
+ return Optional.of(MagicLabelValue.valueOf(value));
+ } catch (IllegalArgumentException e) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelVote.java b/java/com/google/gerrit/server/query/change/MagicLabelVote.java
new file mode 100644
index 0000000..c29ac72
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/MagicLabelVote.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Strings;
+import com.google.gerrit.entities.LabelType;
+import java.util.Locale;
+
+/** An entity representing a special label vote that's not numeric, e.g. MAX, MIN, etc... */
+@AutoValue
+public abstract class MagicLabelVote {
+ public static MagicLabelVote parseWithEquals(String text) {
+ checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+ int e = text.lastIndexOf('=');
+ checkArgument(e >= 0, "Label vote missing '=': %s", text);
+ String label = text.substring(0, e);
+ String voteValue = text.substring(e + 1);
+ return create(label, MagicLabelValue.valueOf(voteValue));
+ }
+
+ public static MagicLabelVote create(String label, MagicLabelValue value) {
+ return new AutoValue_MagicLabelVote(LabelType.checkNameInternal(label), value);
+ }
+
+ public abstract String label();
+
+ public abstract MagicLabelValue value();
+
+ public String formatLabel() {
+ return label().toLowerCase(Locale.US) + "=" + value().name();
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
index 0356cdd..53d0f18 100644
--- a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
+++ b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
@@ -19,7 +19,9 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.AttentionSetInput;
@@ -60,6 +62,7 @@
* This class is used to update the attention set when performing a review or replying on a change.
*/
public class ReplyAttentionSetUpdates {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PermissionBackend permissionBackend;
private final AddToAttentionSetOp.Factory addToAttentionSetOpFactory;
@@ -316,11 +319,15 @@
AttentionSetUtil.validateInput(add);
try {
Account.Id attentionUserId =
- getAccountIdAndValidateUser(changeNotes, add.user, accountsChangedInCommit);
+ getAccountIdAndValidateUser(
+ changeNotes, add.user, accountsChangedInCommit, AttentionSetUpdate.Operation.ADD);
addToAttentionSet(bu, changeNotes, attentionUserId, add.reason, false);
} catch (AccountResolver.UnresolvableAccountException ex) {
// This happens only when the account doesn't exist. Silently ignore it. If we threw an error
// message here, then it would be possible to probe whether an account exists.
+ } catch (AuthException ex) {
+ // adding users without permission to the attention set should fail silently.
+ logger.atFine().log(ex.getMessage());
}
}
@@ -334,17 +341,25 @@
AttentionSetUtil.validateInput(remove);
try {
Account.Id attentionUserId =
- getAccountIdAndValidateUser(changeNotes, remove.user, accountsChangedInCommit);
+ getAccountIdAndValidateUser(
+ changeNotes,
+ remove.user,
+ accountsChangedInCommit,
+ AttentionSetUpdate.Operation.REMOVE);
removeFromAttentionSet(bu, changeNotes, attentionUserId, remove.reason, false);
} catch (AccountResolver.UnresolvableAccountException ex) {
// This happens only when the account doesn't exist. Silently ignore it. If we threw an error
// message here, then it would be possible to probe whether an account exists.
+ } catch (AuthException ex) {
+ // this should never happen since removing users with permissions should work.
+ logger.atSevere().log(ex.getMessage());
}
}
- private Account.Id getAccountId(ChangeNotes changeNotes, String user)
+ private Account.Id getAccountId(
+ ChangeNotes changeNotes, String user, AttentionSetUpdate.Operation operation)
throws ConfigInvalidException, IOException, UnprocessableEntityException,
- PermissionBackendException {
+ PermissionBackendException, AuthException {
Account.Id attentionUserId = accountResolver.resolve(user).asUnique().account().id();
try {
permissionBackend
@@ -352,22 +367,29 @@
.change(changeNotes)
.check(ChangePermission.READ);
} catch (AuthException e) {
+ // If the change is private, it is okay to add the user to the attention set since that
+ // person will be granted visibility when a reviewer.
if (!changeNotes.getChange().isPrivate()) {
- // If the change is private, it is okay to add the user to the attention set since that
- // person will be granted visibility when a reviewer.
- throw new UnprocessableEntityException(
- "Can't add to attention set: Read not permitted for " + attentionUserId, e);
+
+ // Removing users without access is allowed, adding is not allowed
+ if (operation == AttentionSetUpdate.Operation.ADD) {
+ throw new AuthException(
+ "Can't modify attention set: Read not permitted for " + attentionUserId, e);
+ }
}
}
return attentionUserId;
}
private Account.Id getAccountIdAndValidateUser(
- ChangeNotes changeNotes, String user, Set<Account.Id> accountsChangedInCommit)
+ ChangeNotes changeNotes,
+ String user,
+ Set<Account.Id> accountsChangedInCommit,
+ AttentionSetUpdate.Operation operation)
throws ConfigInvalidException, IOException, PermissionBackendException,
- UnprocessableEntityException, BadRequestException {
+ UnprocessableEntityException, BadRequestException, AuthException {
try {
- Account.Id attentionUserId = getAccountId(changeNotes, user);
+ Account.Id attentionUserId = getAccountId(changeNotes, user, operation);
if (accountsChangedInCommit.contains(attentionUserId)) {
throw new BadRequestException(
String.format(
diff --git a/java/com/google/gerrit/truth/NullAwareCorrespondence.java b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
index 687ad94..5b107a6 100644
--- a/java/com/google/gerrit/truth/NullAwareCorrespondence.java
+++ b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
@@ -7,15 +7,6 @@
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 42354ca..976e828 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -4020,6 +4020,86 @@
}
@Test
+ public void submitRequirement_withLabelEqualsMax() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("label:code-review=MAX"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+
+ voteLabel(changeId, "code-review", 2);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+ }
+
+ @Test
+ public void submitRequirement_withLabelEqualsMinBlockingSubmission() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("-label:code-review=MIN"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is satisfied because there are no votes
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+
+ voteLabel(changeId, "code-review", -1);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is still satisfied because -1 is not the max negative value
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+
+ voteLabel(changeId, "code-review", -2);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ // Requirement is now unsatisfied because -2 is the max negative value
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+ }
+
+ @Test
+ public void submitRequirement_withLabelEqualsAny() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(
+ SubmitRequirementExpression.create("label:code-review=ANY"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+
+ voteLabel(changeId, "code-review", 1);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).hasSize(1);
+ assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+ }
+
+ @Test
public void submitRequirementIsSatisfied_whenSubmittabilityExpressionIsFulfilled()
throws Exception {
configSubmitRequirement(
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index eb5bcba..4590d34 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1207,10 +1207,15 @@
ChangeApi originalChange = gApi.changes().id(project.get() + "~master~" + result.getChangeId());
ChangeApi cherryPick = originalChange.revision(result.getCommit().name()).cherryPick(input);
+ String firstCherryPickChangeId = cherryPick.id();
cherryPick.setWorkInProgress();
- cherryPick = originalChange.revision(result.getCommit().name()).cherryPick(input);
+ gApi.changes()
+ .id(project.get() + "~master~" + result.getChangeId())
+ .revision(result.getCommit().name())
+ .cherryPick(input);
- ChangeInfo secondCherryPickResult = cherryPick.get(ALL_REVISIONS);
+ ChangeInfo secondCherryPickResult =
+ gApi.changes().id(firstCherryPickChangeId).get(ALL_REVISIONS);
assertThat(secondCherryPickResult.revisions).hasSize(2);
assertThat(secondCherryPickResult.workInProgress).isNull();
}
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index b7acbe2..16ccc35 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -38,7 +38,6 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.AccountGroup;
-import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
@@ -69,7 +68,6 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
@@ -1106,37 +1104,10 @@
@Test
public void receivePackOmitsMissingObject() throws Exception {
- String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
try (Repository repo = repoManager.openRepository(project);
TestRepository<Repository> tr = new TestRepository<>(repo)) {
- String subject = "Subject for missing commit";
- Change c = new Change(cd3.change());
- PatchSet.Id psId = PatchSet.id(cd3.getId(), 2);
- c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
-
- PersonIdent committer = serverIdent.get();
- PersonIdent author =
- noteUtil.newAccountIdIdent(getAccount(admin.id()).id(), committer.getWhen(), committer);
- tr.branch(RefNames.changeMetaRef(cd3.getId()))
- .commit()
- .author(author)
- .committer(committer)
- .message(
- "Update patch set "
- + psId.get()
- + "\n"
- + "\n"
- + "Patch-set: "
- + psId.get()
- + "\n"
- + "Commit: "
- + rev
- + "\n"
- + "Subject: "
- + subject
- + "\n")
- .create();
- indexer.index(c.getProject(), c.getId());
+ PatchSet.Id psId = PatchSet.id(cd3.getId(), 1);
+ tr.delete(psId.toRefName());
}
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd4, 1));
diff --git a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
index cad0b83..cac376f 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
@@ -31,14 +31,18 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.server.index.GerritIndexStatus;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.inject.Injector;
+import com.google.inject.Key;
import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
import java.nio.file.Files;
+import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.jgit.lib.Config;
@@ -48,9 +52,6 @@
@NoHttpd
public abstract class AbstractReindexTests extends StandaloneSiteTest {
- /** @param injector injector */
- public abstract void configureIndex(Injector injector) throws Exception;
-
private static final String CHANGES = ChangeSchemaDefinitions.NAME;
private Project.NameKey project;
@@ -223,10 +224,18 @@
}
}
+ protected static void createAllIndexes(Injector injector) {
+ Collection<IndexDefinition<?, ?, ?>> indexDefs =
+ injector.getInstance(Key.get(new TypeLiteral<Collection<IndexDefinition<?, ?, ?>>>() {}));
+ for (IndexDefinition<?, ?, ?> indexDef : indexDefs) {
+ indexDef.getIndexCollection().getSearchIndex().deleteAll();
+ }
+ }
+
private void setUpChange() throws Exception {
project = Project.nameKey("reindex-project-test");
try (ServerContext ctx = startServer()) {
- configureIndex(ctx.getInjector());
+ createAllIndexes(ctx.getInjector());
GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
gApi.projects().create(project.get());
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index f23cc10..0632241 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -14,23 +14,15 @@
package com.google.gerrit.acceptance.pgm;
-import static com.google.gerrit.elasticsearch.ElasticTestUtils.createAllIndexes;
import static com.google.gerrit.elasticsearch.ElasticTestUtils.getConfig;
import com.google.gerrit.elasticsearch.ElasticVersion;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
public class ElasticReindexIT extends AbstractReindexTests {
-
@ConfigSuite.Default
public static Config elasticsearchV7() {
return getConfig(ElasticVersion.V7_8);
}
-
- @Override
- public void configureIndex(Injector injector) {
- createAllIndexes(injector);
- }
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
similarity index 68%
rename from javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java
rename to javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
index 223851e..e630bca 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/LuceneReindexIT.java
@@ -14,9 +14,14 @@
package com.google.gerrit.acceptance.pgm;
-import com.google.inject.Injector;
+import com.google.gerrit.testing.ConfigSuite;
+import org.eclipse.jgit.lib.Config;
-public class ReindexIT extends AbstractReindexTests {
- @Override
- public void configureIndex(Injector injector) {}
+public class LuceneReindexIT extends AbstractReindexTests {
+ @ConfigSuite.Default
+ public static Config luceneConfig() {
+ Config cfg = new Config();
+ cfg.setString("index", null, "type", "lucene");
+ return cfg;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index 03e4bfb..4b45476 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.extensions.restapi.testing.AttentionSetUpdateSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -32,23 +33,29 @@
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.AttentionSetUpdate.Operation;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Permission;
import com.google.gerrit.extensions.api.changes.AttentionSetInput;
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewerInput;
+import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.account.ServiceUserClassifier;
import com.google.gerrit.server.query.change.ChangeData;
@@ -82,6 +89,7 @@
@Inject private FakeEmailSender email;
@Inject private TestCommentHelper testCommentHelper;
@Inject private Provider<InternalChangeQuery> changeQueryProvider;
+ @Inject private ProjectOperations projectOperations;
/** Simulates a fake clock. Uses second granularity. */
private static class FakeClock implements LongSupplier {
@@ -1835,6 +1843,44 @@
assertThat(attentionSet).hasReasonThat().isEqualTo("Their vote was deleted");
}
+ @Test
+ public void accountsWithNoReadPermissionIgnoredOnReply() throws Exception {
+ // Create a group with user.
+ GroupInput groupInput = new GroupInput();
+ groupInput.name = name("User");
+ groupInput.members = ImmutableList.of(String.valueOf(user.id()));
+ GroupInfo group = gApi.groups().create(groupInput).get();
+
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).addReviewer(user.email());
+
+ // remove read permission for user.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(AccountGroup.uuid(group.id)))
+ .update();
+
+ // removing user without permissions from attention set is allowed on reply.
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(new ReviewInput().removeUserFromAttentionSet(user.email(), "reason"));
+
+ // Add user to attention throws an exception.
+ assertThrows(
+ AuthException.class,
+ () -> change(r).addToAttentionSet(new AttentionSetInput(user.email(), "reason")));
+
+ // Add user to attention set is ignored on reply.
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(new ReviewInput().addUserToAttentionSet(user.email(), "reason"));
+ assertThat(Iterables.getOnlyElement(getAttentionSetUpdatesForUser(r, user)).operation())
+ .isEqualTo(Operation.REMOVE);
+ }
+
private List<AttentionSetUpdate> getAttentionSetUpdatesForUser(
PushOneCommit.Result r, TestAccount account) {
return getAttentionSetUpdates(r.getChange().getId()).stream()
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index e39f967..9d821b7 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -257,7 +257,6 @@
+ "Groups: "
+ rev
+ "\n");
- indexer.index(c.getProject(), c.getId());
ChangeNotes notes = changeNotesFactory.create(c.getProject(), c.getId());
FixInput fix = new FixInput();
@@ -817,8 +816,6 @@
+ "Subject: "
+ subject
+ "\n");
- indexer.index(c.getProject(), c.getId());
-
return ps;
}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
new file mode 100644
index 0000000..f5e4e09
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.AbstractFakeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class DefaultIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void fakeIsBoundByDefault() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEmpty();
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(AbstractFakeIndex.FakeChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
new file mode 100644
index 0000000..4122426
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.testing.AbstractFakeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class FakeIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "fake");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void fakeIsBoundWhenConfigured() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("fake");
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(AbstractFakeIndex.FakeChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
new file mode 100644
index 0000000..31e31fd
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.testsuite.index;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.lucene.LuceneChangeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import javax.inject.Inject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test to check that the expected index backend was bound depending on sys/env properties. */
+public class LuceneIndexBindingIT extends AbstractDaemonTest {
+
+ @Inject private ChangeIndexCollection changeIndex;
+
+ private static String propertyBeforeTest;
+
+ @BeforeClass
+ public static void setup() {
+ propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
+ System.setProperty(IndexType.SYS_PROP, "lucene");
+ }
+
+ @AfterClass
+ public static void teardown() {
+ System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
+ }
+
+ @Test
+ public void luceneIsBoundWhenConfigured() throws Exception {
+ assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("lucene");
+ assertThat(changeIndex.getSearchIndex()).isInstanceOf(LuceneChangeIndex.class);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 3c10fbc..1f29f45 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1007,6 +1007,7 @@
changes.put(-1, reviewMinus1Change);
changes.put(-2, reviewMinus2Change);
+ assertQuery("label:Code-Review=MIN", reviewMinus2Change);
assertQuery("label:Code-Review=-2", reviewMinus2Change);
assertQuery("label:Code-Review-2", reviewMinus2Change);
assertQuery("label:Code-Review=-1", reviewMinus1Change);
@@ -1018,6 +1019,13 @@
assertQuery("label:Code-Review=+2", reviewPlus2Change);
assertQuery("label:Code-Review=2", reviewPlus2Change);
assertQuery("label:Code-Review+2", reviewPlus2Change);
+ assertQuery("label:Code-Review=MAX", reviewPlus2Change);
+ assertQuery(
+ "label:Code-Review=ANY",
+ reviewPlus2Change,
+ reviewPlus1Change,
+ reviewMinus1Change,
+ reviewMinus2Change);
assertQuery("label:Code-Review>-3", codeReviewInRange(changes, -2, 2));
assertQuery("label:Code-Review>=-2", codeReviewInRange(changes, -2, 2));
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 23c0e14..9dcb67e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -677,7 +677,8 @@
patchRange?: PatchRange,
file?: NormalizedFileInfo
) {
- const draftCount = changeComments?.computeDraftCountForFile(
+ if (changeComments === undefined) return '';
+ const draftCount = changeComments.computeDraftCountForFile(
patchRange,
file
);
@@ -693,7 +694,8 @@
patchRange?: PatchRange,
file?: NormalizedFileInfo
) {
- const draftCount = changeComments?.computeDraftCountForFile(
+ if (changeComments === undefined) return '';
+ const draftCount = changeComments.computeDraftCountForFile(
patchRange,
file
);
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index d341083..fae624e 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -90,9 +90,9 @@
*/
function computeThreads(
message: CombinedMessage,
- changeComments: ChangeComments
+ changeComments?: ChangeComments
): CommentThread[] {
- if (message._index === undefined) {
+ if (message._index === undefined || changeComments === undefined) {
return [];
}
const messageId = getMessageId(message);
@@ -369,7 +369,7 @@
return combinedMessages;
}
- getCommentThreads(message: CombinedMessage, changeComments: ChangeComments) {
+ getCommentThreads(message: CombinedMessage, changeComments?: ChangeComments) {
return computeThreads(message, changeComments);
}
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
index 6178e7a..4381a59 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.ts
@@ -31,6 +31,7 @@
import {hasOwnProperty} from '../../../utils/common-util';
import {ProjectWatchInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
+import {IronInputElement} from '@polymer/iron-input';
const NOTIFICATION_TYPES = [
{name: 'Changes', key: 'notify_new_changes'},
@@ -43,9 +44,11 @@
export interface GrWatchedProjectsEditor {
$: {
newFilter: HTMLInputElement;
+ newFilterInput: IronInputElement;
newProject: GrAutocomplete;
};
}
+
@customElement('gr-watched-projects-editor')
export class GrWatchedProjectsEditor extends PolymerElement {
static get template() {
@@ -62,7 +65,7 @@
_projectsToRemove: ProjectWatchInfo[] = [];
@property({type: Object})
- _query?: AutocompleteQuery;
+ _query: AutocompleteQuery;
private readonly restApiService = appContext.restApiService;
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
index edc8fb2..fb65a03 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts
@@ -102,13 +102,13 @@
</th>
<th colspan$="[[_getTypeCount()]]">
<iron-input
+ id="newFilterInput"
class="newFilterInput"
placeholder="branch:name, or other search expression"
>
<input
id="newFilter"
class="newFilterInput"
- is="iron-input"
placeholder="branch:name, or other search expression"
/>
</iron-input>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
similarity index 76%
rename from polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js
rename to polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
index aac8995..cb4b86d 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
@@ -15,14 +15,18 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-watched-projects-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-watched-projects-editor';
+import {GrWatchedProjectsEditor} from './gr-watched-projects-editor';
+import {stubRestApi} from '../../../test/test-utils';
+import {ProjectWatchInfo} from '../../../types/common';
+import {queryAll, queryAndAssert} from '../../../test/test-utils';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
const basicFixture = fixtureFromElement('gr-watched-projects-editor');
suite('gr-watched-projects-editor tests', () => {
- let element;
+ let element: GrWatchedProjectsEditor;
setup(done => {
const projects = [
@@ -30,29 +34,34 @@
project: 'project a',
notify_submitted_changes: true,
notify_abandoned_changes: true,
- }, {
+ },
+ {
project: 'project b',
filter: 'filter 1',
notify_new_changes: true,
- }, {
+ },
+ {
project: 'project b',
filter: 'filter 2',
- }, {
+ },
+ {
project: 'project c',
notify_new_changes: true,
notify_new_patch_sets: true,
notify_all_comments: true,
},
- ];
+ ] as ProjectWatchInfo[];
stubRestApi('getWatchedProjects').returns(Promise.resolve(projects));
stubRestApi('getSuggestedProjects').callsFake(input => {
if (input.startsWith('th')) {
- return Promise.resolve({'the project': {
- id: 'the project',
- state: 'ACTIVE',
- web_links: [],
- }});
+ return Promise.resolve({
+ 'the project': {
+ id: 'the project',
+ state: 'ACTIVE',
+ web_links: [],
+ },
+ });
} else {
return Promise.resolve({});
}
@@ -60,18 +69,18 @@
element = basicFixture.instantiate();
- element.loadData().then(() => { flush(done); });
+ element.loadData().then(() => {
+ flush(done);
+ });
});
test('renders', () => {
- const rows = element.shadowRoot
- .querySelector('table').querySelectorAll('tbody tr');
+ const rows = queryAndAssert(element, 'table').querySelectorAll('tbody tr');
assert.equal(rows.length, 4);
- function getKeysOfRow(row) {
- const boxes = rows[row].querySelectorAll('input[checked]');
- return Array.prototype.map.call(boxes,
- e => e.getAttribute('data-key'));
+ function getKeysOfRow(row: number) {
+ const boxes = queryAll(rows[row], 'input[checked]');
+ return Array.prototype.map.call(boxes, e => e.getAttribute('data-key'));
}
let checkedKeys = getKeysOfRow(0);
@@ -157,41 +166,44 @@
test('_handleAddProject', () => {
element.$.newProject.value = 'project d';
element.$.newProject.setText('project d');
- element.$.newFilter.bindValue = '';
+ element.$.newFilterInput.bindValue = '';
element._handleAddProject();
- assert.equal(element._projects.length, 5);
- assert.equal(element._projects[4].project, 'project d');
- assert.isNotOk(element._projects[4].filter);
- assert.isTrue(element._projects[4]._is_local);
+ const projects = element._projects!;
+ assert.equal(projects.length, 5);
+ assert.equal(projects[4].project, 'project d');
+ assert.isNotOk(projects[4].filter);
+ assert.isTrue(projects[4]._is_local);
});
test('_handleAddProject with invalid inputs', () => {
element.$.newProject.value = 'project b';
element.$.newProject.setText('project b');
- element.$.newFilter.bindValue = 'filter 1';
+ element.$.newFilterInput.bindValue = 'filter 1';
element.$.newFilter.value = 'filter 1';
element._handleAddProject();
- assert.equal(element._projects.length, 4);
+ assert.equal(element._projects!.length, 4);
});
test('_handleRemoveProject', () => {
- assert.equal(element._projectsToRemove, 0);
- const button = element.shadowRoot
- .querySelector('table tbody tr:nth-child(2) gr-button');
+ assert.deepEqual(element._projectsToRemove, []);
+
+ const button = queryAndAssert(
+ element,
+ 'table tbody tr:nth-child(2) gr-button'
+ );
MockInteractions.tap(button);
flush();
- const rows = element.shadowRoot
- .querySelector('table tbody').querySelectorAll('tr');
+ const rows = queryAndAssert(element, 'table tbody').querySelectorAll('tr');
+
assert.equal(rows.length, 3);
assert.equal(element._projectsToRemove.length, 1);
assert.equal(element._projectsToRemove[0].project, 'project b');
});
});
-
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index f04e224..bcdab0e 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -405,6 +405,14 @@
packageLicenseFile: "LICENSE",
}
},
+ {
+ name: "immer",
+ license: {
+ name: "immer",
+ type: LicenseTypes.Mit,
+ packageLicenseFile: "LICENSE",
+ }
+ }
];
export default packages;
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 4e18e47..d26dc97 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -37,6 +37,7 @@
"@webcomponents/webcomponentsjs": "^1.3.3",
"ba-linkify": "file:../../lib/ba-linkify/src/",
"codemirror-minified": "^5.62.0",
+ "immer": "^9.0.5",
"lit-element": "^2.5.1",
"page": "^1.11.6",
"polymer-bridges": "file:../../polymer-bridges/",
diff --git a/polygerrit-ui/app/services/comments/comments-model.ts b/polygerrit-ui/app/services/comments/comments-model.ts
index 87f42c1..b26ec9b 100644
--- a/polygerrit-ui/app/services/comments/comments-model.ts
+++ b/polygerrit-ui/app/services/comments/comments-model.ts
@@ -130,6 +130,7 @@
d => d.__draftID === draft.__draftID || d.id === draft.id
);
if (index === -1) return;
- drafts[draft.path] = [...drafts[draft.path]].splice(index, 1);
+ drafts[draft.path] = [...drafts[draft.path]];
+ drafts[draft.path].splice(index, 1);
privateState$.next(nextState);
}
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 2cc6f41..0df7d12 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -419,7 +419,10 @@
eventInfo.inBackgroundTab = isInBackgroundTab;
}
- if (this._flagsService.enabledExperiments.length) {
+ if (
+ name === Timing.APP_STARTED &&
+ this._flagsService.enabledExperiments.length
+ ) {
eventInfo.enabledExperiments = JSON.stringify(
this._flagsService.enabledExperiments
);
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 826794e..0f2608e 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -984,6 +984,7 @@
notify_all_comments?: boolean;
notify_submitted_changes?: boolean;
notify_abandoned_changes?: boolean;
+ _is_local?: boolean; // Added manually
}
/**
* The DeleteDraftCommentsInput entity contains information specifying a set of draft comments that should be deleted
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index d8657f7..4fb98dd 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -600,6 +600,11 @@
agent-base "6"
debug "4"
+immer@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.5.tgz#a7154f34fe7064f15f00554cc94c66cc0bf453ec"
+ integrity sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"