Merge "Update rules_closure to latest version" into stable-2.16
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 0000000..a98975b
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,32 @@
+- job:
+    name: gerrit-base
+    parent: gerrit-setup
+    description: |
+      Base job for all Gerrit-related builds
+
+      This adds required projects needed for all Gerrit-related builds
+      (i.e., builds of Gerrit itself or plugins) on this branch.
+    # No additional required projects required for this branch.
+
+- job:
+    name: gerrit-build
+    parent: gerrit-build-base
+    description: |
+      Build Gerrit
+
+      This builds Gerrit with the core plugins.
+    required-projects:
+      # This inherits from gerrit-base, so submodules listed above do
+      # not need to be repeated here.
+      - plugins/codemirror-editor
+      - plugins/commit-message-length-validator
+      - plugins/download-commands
+      - plugins/hooks
+      - plugins/replication
+      - plugins/reviewnotes
+      - plugins/singleusergroup
+
+- project:
+    check:
+      jobs:
+        - gerrit-build
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c908f99..8637567 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3175,7 +3175,7 @@
 [[elasticsearch.numberOfShards]]elasticsearch.numberOfShards::
 +
 Sets the number of shards to use per index. Refer to the
-link:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-concepts.html#getting-started-shards-and-replicas[
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings[
 Elasticsearch documentation] for details.
 +
 Defaults to 5 for Elasticsearch versions 5 and 6, and to 1 starting with Elasticsearch 7.
@@ -3183,11 +3183,19 @@
 [[elasticsearch.numberOfReplicas]]elasticsearch.numberOfReplicas::
 +
 Sets the number of replicas to use per index. Refer to the
-link:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-concepts.html#getting-started-shards-and-replicas[
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings[
 Elasticsearch documentation] for details.
 +
 Defaults to 1.
 
+[[elasticsearch.maxResultWindow]]elasticsearch.maxResultWindow::
++
+Sets the maximum value of `from + size` for searches to use per index. Refer to the
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings[
+Elasticsearch documentation] for details.
++
+Defaults to 10000.
+
 ==== Elasticsearch Security
 
 When security is enabled in Elasticsearch, the username and password must be provided.
@@ -5059,6 +5067,10 @@
 The link:#schedule-configuration-interval[interval] for running
 account deactivations.
 
+Note that the task will only be scheduled if the
+link:#autoUpdateAccountActiveStatus[auth.autoUpdateAccountActiveStatus]
+is set to true.
+
 link:#schedule-configuration-examples[Schedule examples] can be found
 in the link:#schedule-configuration[Schedule Configuration] section.
 
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index cbe9bc7..35c33cb 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -40,10 +40,13 @@
   static final String KEY_SERVER = "server";
   static final String KEY_NUMBER_OF_SHARDS = "numberOfShards";
   static final String KEY_NUMBER_OF_REPLICAS = "numberOfReplicas";
+  static final String KEY_MAX_RESULT_WINDOW = "maxResultWindow";
+
   static final String DEFAULT_PORT = "9200";
   static final String DEFAULT_USERNAME = "elastic";
   static final int DEFAULT_NUMBER_OF_SHARDS = 0;
   static final int DEFAULT_NUMBER_OF_REPLICAS = 1;
+  static final int DEFAULT_MAX_RESULT_WINDOW = 10000;
 
   private final Config cfg;
   private final List<HttpHost> hosts;
@@ -52,6 +55,7 @@
   final String password;
   final int numberOfShards;
   final int numberOfReplicas;
+  final int maxResultWindow;
   final String prefix;
 
   @Inject
@@ -68,6 +72,8 @@
         cfg.getInt(SECTION_ELASTICSEARCH, null, KEY_NUMBER_OF_SHARDS, DEFAULT_NUMBER_OF_SHARDS);
     this.numberOfReplicas =
         cfg.getInt(SECTION_ELASTICSEARCH, null, KEY_NUMBER_OF_REPLICAS, DEFAULT_NUMBER_OF_REPLICAS);
+    this.maxResultWindow =
+        cfg.getInt(SECTION_ELASTICSEARCH, null, KEY_MAX_RESULT_WINDOW, DEFAULT_MAX_RESULT_WINDOW);
     this.hosts = new ArrayList<>();
     for (String server : cfg.getStringList(SECTION_ELASTICSEARCH, null, KEY_SERVER)) {
       try {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticSetting.java b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
index 14e4623..e016efb 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticSetting.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
@@ -35,6 +35,7 @@
       properties.analysis = fields.build();
       properties.numberOfShards = config.getNumberOfShards(adapter);
       properties.numberOfReplicas = config.numberOfReplicas;
+      properties.maxResultWindow = config.maxResultWindow;
       return properties;
     }
 
@@ -75,6 +76,7 @@
     Map<String, FieldProperties> analysis;
     Integer numberOfShards;
     Integer numberOfReplicas;
+    Integer maxResultWindow;
   }
 
   static class FieldProperties {
diff --git a/java/com/google/gerrit/server/account/AccountDeactivator.java b/java/com/google/gerrit/server/account/AccountDeactivator.java
index b0dc527..ac02322 100644
--- a/java/com/google/gerrit/server/account/AccountDeactivator.java
+++ b/java/com/google/gerrit/server/account/AccountDeactivator.java
@@ -57,10 +57,14 @@
 
     @Override
     public void start() {
-      if (!supportAutomaticAccountActivityUpdate) {
-        return;
+      if (schedule.isPresent()) {
+        if (supportAutomaticAccountActivityUpdate) {
+          queue.scheduleAtFixedRate(deactivator, schedule.get());
+        } else {
+          logger.atWarning().log(
+              "Not scheduling AccountDeactivator because auth.autoUpdateAccountActiveStatus is false");
+        }
       }
-      schedule.ifPresent(s -> queue.scheduleAtFixedRate(deactivator, s));
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 10b3bc8..61726b8 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -22,6 +22,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Enums;
 import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.GroupDescription;
@@ -913,12 +914,10 @@
       return isVisible();
     }
     Set<Account.Id> m = args.accountResolver.findAll(who);
-    if (!m.isEmpty()) {
-      List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
-      for (Account.Id id : m) {
-        return visibleto(args.userFactory.create(id));
-      }
-      return Predicate.or(p);
+    if (m.size() == 1) {
+      return visibleto(args.userFactory.create(Iterables.getOnlyElement(m)));
+    } else if (m.size() > 1) {
+      throw error(String.format("\"%s\" resolves to multiple accounts", who));
     }
 
     // If its not an account, maybe its a group?
diff --git a/java/com/google/gerrit/server/schema/Schema_151.java b/java/com/google/gerrit/server/schema/Schema_151.java
index 41d8a32..bab9f41 100644
--- a/java/com/google/gerrit/server/schema/Schema_151.java
+++ b/java/com/google/gerrit/server/schema/Schema_151.java
@@ -14,11 +14,15 @@
 
 package com.google.gerrit.server.schema;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -26,6 +30,7 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Optional;
 
 /** A schema which adds the 'created on' field to groups. */
@@ -37,6 +42,13 @@
 
   @Override
   protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+    Connection connection = ((JdbcSchema) db).getConnection();
+    if (!createdOnColumnExists(connection)) {
+      try (Statement stmt = connection.createStatement()) {
+        stmt.execute("ALTER TABLE account_groups ADD COLUMN created_on TIMESTAMP NULL");
+      }
+    }
+
     try (PreparedStatement groupUpdate =
             prepareStatement(db, "UPDATE account_groups SET created_on = ? WHERE group_id = ?");
         PreparedStatement addedOnRetrieval =
@@ -56,6 +68,20 @@
     }
   }
 
+  @VisibleForTesting
+  public static boolean createdOnColumnExists(Connection connection) throws SQLException {
+    DatabaseMetaData metaData = connection.getMetaData();
+    boolean toUpper = metaData.storesUpperCaseIdentifiers();
+    return metaData
+        .getColumns(
+            null, null, convertCase(toUpper, "account_groups"), convertCase(toUpper, "created_on"))
+        .next();
+  }
+
+  private static String convertCase(boolean toUpper, String input) {
+    return toUpper ? input.toUpperCase(Locale.US) : input;
+  }
+
   private static Optional<Timestamp> getFirstTimeMentioned(
       PreparedStatement addedOnRetrieval, AccountGroup.Id groupId) throws SQLException {
     addedOnRetrieval.setInt(1, groupId.get());
diff --git a/java/com/google/gerrit/util/logging/BUILD b/java/com/google/gerrit/util/logging/BUILD
index b8db49bd..ee598a4 100644
--- a/java/com/google/gerrit/util/logging/BUILD
+++ b/java/com/google/gerrit/util/logging/BUILD
@@ -8,6 +8,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//lib:gson",
+        "//lib/flogger:api",
         "//lib/log:log4j",
     ],
 )
diff --git a/java/com/google/gerrit/util/logging/NamedFluentLogger.java b/java/com/google/gerrit/util/logging/NamedFluentLogger.java
new file mode 100644
index 0000000..04fc18d
--- /dev/null
+++ b/java/com/google/gerrit/util/logging/NamedFluentLogger.java
@@ -0,0 +1,84 @@
+// 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
+// limitations under the License.
+
+package com.google.gerrit.util.logging;
+
+import com.google.common.flogger.AbstractLogger;
+import com.google.common.flogger.LogContext;
+import com.google.common.flogger.LoggingApi;
+import com.google.common.flogger.backend.LoggerBackend;
+import com.google.common.flogger.backend.Platform;
+import com.google.common.flogger.parser.DefaultPrintfMessageParser;
+import com.google.common.flogger.parser.MessageParser;
+import java.util.logging.Level;
+
+/**
+ * FluentLogger.forEnclosingClass() searches for caller class name and passes it as String to
+ * constructor FluentLogger.FluentLogger(LoggerBackend) (which is package protected).
+ *
+ * <p>This allows to create NamedFluentLogger with given name so that dedicated configuration can be
+ * specified by a custom appender in the log4j.properties file. An example of this is the logger
+ * used by the replication queue in the replication plugin, and gerrit's Garbage Collection log.
+ */
+public class NamedFluentLogger extends AbstractLogger<NamedFluentLogger.Api> {
+  /** Copied from FluentLogger */
+  public interface Api extends LoggingApi<Api> {}
+
+  /** Copied from FluentLogger */
+  private static final class NoOp extends LoggingApi.NoOp<Api> implements Api {}
+
+  private static final NoOp NO_OP = new NoOp();
+
+  public static NamedFluentLogger forName(String name) {
+    return new NamedFluentLogger(Platform.getBackend(name));
+  }
+
+  private NamedFluentLogger(LoggerBackend backend) {
+    super(backend);
+  }
+
+  @Override
+  public Api at(Level level) {
+    boolean isLoggable = isLoggable(level);
+    boolean isForced = Platform.shouldForceLogging(getName(), level, isLoggable);
+    return (isLoggable || isForced) ? new Context(level, isForced) : NO_OP;
+  }
+
+  /** Copied from FluentLogger */
+  private final class Context extends LogContext<NamedFluentLogger, Api> implements Api {
+    private Context(Level level, boolean isForced) {
+      super(level, isForced);
+    }
+
+    @Override
+    protected NamedFluentLogger getLogger() {
+      return NamedFluentLogger.this;
+    }
+
+    @Override
+    protected Api api() {
+      return this;
+    }
+
+    @Override
+    protected Api noOp() {
+      return NO_OP;
+    }
+
+    @Override
+    protected MessageParser getMessageParser() {
+      return DefaultPrintfMessageParser.getInstance();
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 0ebbdc9..bd2b359 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1710,16 +1710,43 @@
     assertQuery(q + " visibleto:self", change2, change1);
 
     // Second user cannot see first user's private change
-    Account.Id user2 = createAccount("anotheruser");
+    Account.Id user2 = createAccount("user2");
     assertQuery(q + " visibleto:" + user2.get(), change1);
-    assertQuery(q + " visibleto:anotheruser", change1);
+    assertQuery(q + " visibleto:user2", change1);
 
     String g1 = createGroup("group1", "Administrators");
-    gApi.groups().id(g1).addMembers("anotheruser");
+    gApi.groups().id(g1).addMembers("user2");
     assertQuery(q + " visibleto:" + g1, change1);
 
     requestContext.setContext(newRequestContext(user2));
     assertQuery("is:visible", change1);
+
+    Account.Id user3 = createAccount("user3");
+
+    // Explicitly authenticate user2 and user3 so that display name gets set
+    AuthRequest authRequest = AuthRequest.forUser("user2");
+    authRequest.setDisplayName("Another User");
+    authRequest.setEmailAddress("user2@example.com");
+    accountManager.authenticate(authRequest);
+    authRequest = AuthRequest.forUser("user3");
+    authRequest.setDisplayName("Another User");
+    authRequest.setEmailAddress("user3@example.com");
+    accountManager.authenticate(authRequest);
+
+    // Switch to user3
+    requestContext.setContext(newRequestContext(user3));
+    Change change3 = insert(repo, newChange(repo), user3);
+    Change change4 = insert(repo, newChangePrivate(repo), user3);
+
+    // User3 can see both their changes and the first user's change
+    assertQuery(q + " visibleto:" + user3.get(), change4, change3, change1);
+
+    // User2 cannot see user3's private change
+    assertQuery(q + " visibleto:" + user2.get(), change3, change1);
+
+    // Query as user3 by display name matching user2 and user3; bad request
+    assertFailingQuery(
+        q + " visibleto:\"Another User\"", "\"Another User\" resolves to multiple accounts");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java b/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
index 4d5db6d..2e268ee 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.server.schema.Schema_151.createdOnColumnExists;
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -151,6 +152,17 @@
     assertThat(createdOn).isEqualTo(AccountGroup.auditCreationInstantTs());
   }
 
+  @Test
+  public void createdOnIsAddedWhenItIsMissing() throws Exception {
+    assertThat(createdOnColumnExists(connection)).isTrue();
+    try (Statement deleteColumn = connection.createStatement()) {
+      deleteColumn.execute("ALTER TABLE account_groups DROP COLUMN created_on");
+    }
+    assertThat(createdOnColumnExists(connection)).isFalse();
+    schema151.migrateData(db, new TestUpdateUI());
+    assertThat(createdOnColumnExists(connection)).isTrue();
+  }
+
   private AccountGroup.Id createGroupInReviewDb(String name) throws Exception {
     AccountGroup group =
         new AccountGroup(
diff --git a/plugins/BUILD b/plugins/BUILD
index 7d0a2ed..a33ce56 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -50,6 +50,7 @@
     "//java/com/google/gerrit/server/util/time",
     "//java/com/google/gerrit/util/cli",
     "//java/com/google/gerrit/util/http",
+    "//java/com/google/gerrit/util/logging",
     "//lib/commons:compress",
     "//lib/commons:dbcp",
     "//lib/commons:lang",
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 2d3f265..59942b1 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 2d3f265ab1797d4179cbd6855c937989175d5ce5
+Subproject commit 59942b1adf1c949f3633f60ac42f67fae03b3255
diff --git a/plugins/replication b/plugins/replication
index 04bbb43..425f4f2 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 04bbb43e28f14b61412b71ecae19921ab67d62cc
+Subproject commit 425f4f20218986615593da1a9319466743ee12eb
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 341d5a9..f85dca2 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-acceptance-framework</artifactId>
-  <version>2.16.17-SNAPSHOT</version>
+  <version>2.16.18-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index ffb0158..892f954 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>2.16.17-SNAPSHOT</version>
+  <version>2.16.18-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index 89a0855..8aef367 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>2.16.17-SNAPSHOT</version>
+  <version>2.16.18-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-plugin-gwtui_pom.xml b/tools/maven/gerrit-plugin-gwtui_pom.xml
index 8867410..e4e6adb 100644
--- a/tools/maven/gerrit-plugin-gwtui_pom.xml
+++ b/tools/maven/gerrit-plugin-gwtui_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwtui</artifactId>
-  <version>2.16.17-SNAPSHOT</version>
+  <version>2.16.18-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin GWT UI</name>
   <description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index d21e9e4..0e99675 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>2.16.17-SNAPSHOT</version>
+  <version>2.16.18-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index df79f83..0c28b2c 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = "2.16.17-SNAPSHOT"
+GERRIT_VERSION = "2.16.18-SNAPSHOT"