Merge "Upgrade gitiles-servlet and blame-cache to 0.4" into stable-3.2
diff --git a/.gitignore b/.gitignore
index 93c1339..3a5d28b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # Keep following lines sorted according to `LC_COLLATE=C sort`
 *.eml
 *.iml
+*.log
 *.pyc
 *.sublime-*
 *.swp
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 3653241..39bdd6a 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -387,7 +387,7 @@
 `GERRIT_LOG_LEVEL=debug` environment variable:
 
 ----
-  bazel test --test_filter=com.gerrit.server.notedb.ChangeNotesTest \
+  bazel test --test_filter=com.google.gerrit.server.notedb.ChangeNotesTest \
   --test_env=GERRIT_LOG_LEVEL=debug \
   javatests/com/google/gerrit/server:server_tests
 ----
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 6d897ec..8fe8f4e 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -170,6 +170,17 @@
 gets automatically generated by the scenario. Any property setting for it is therefore not
 applicable. Its usage differs from the non-prefixed `PROJECT` keyword, in that sense.
 
+The following core property can be optionally set depending on the runtime environment. The test
+environments used as reference for scenarios development assume its default value, `1.0`. For
+slower or more complex execution environments, the value can be increased this way for example:
+
+* `-Dcom.google.gerrit.scenarios.power_factor=1.5`
+
+This will make the scenario steps take half more time to expect proper completion. A value smaller
+than the default, say `0.8`, will make scenarios wait somewhat less than how they were developed.
+Scenario development is often done using locally running Gerrit systems under test, which are
+sometimes dockerized.
+
 == How to run tests
 
 Run all tests:
diff --git a/WORKSPACE b/WORKSPACE
index 4bf53d8..104e727 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -663,18 +663,18 @@
     sha1 = "a3ae34e57fa8a4040e28247291d0cc3d6b8c7bcf",
 )
 
-AUTO_VALUE_VERSION = "1.7"
+AUTO_VALUE_VERSION = "1.7.1"
 
 maven_jar(
     name = "auto-value",
     artifact = "com.google.auto.value:auto-value:" + AUTO_VALUE_VERSION,
-    sha1 = "fe8387764ed19460eda4f106849c664f51c07121",
+    sha1 = "b995de0a53b58b59c08a3d6f4f9379b29b8e4bdc",
 )
 
 maven_jar(
     name = "auto-value-annotations",
     artifact = "com.google.auto.value:auto-value-annotations:" + AUTO_VALUE_VERSION,
-    sha1 = "5be124948ebdc7807df68207f35a0f23ce427f29",
+    sha1 = "58d76a9ec581f7c6d33f3343de9b2ba04a0ae799",
 )
 
 declare_nongoogle_deps()
@@ -912,48 +912,48 @@
     sha1 = "7e060dd5b19431e6d198e91ff670644372f60fbd",
 )
 
-JETTY_VERS = "9.4.24.v20191120"
+JETTY_VERS = "9.4.27.v20200227"
 
 maven_jar(
     name = "jetty-servlet",
     artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VERS,
-    sha1 = "ca1803fde51b795c0a8346ca8bc6277d9d04d01d",
+    sha1 = "c6354d1e53c41f839ae56f4d8622c866a1ad8487",
 )
 
 maven_jar(
     name = "jetty-security",
     artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VERS,
-    sha1 = "9fa640d36c088cf55843900043d28aef830ade4d",
+    sha1 = "aead56f2a1ac49d720a192cb7c1568e61e34ddae",
 )
 
 maven_jar(
     name = "jetty-server",
     artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VERS,
-    sha1 = "7885cc3d5d7701a444acada7ab97f89846514875",
+    sha1 = "4ef690ce1277e3767d457f87621f2c436a001881",
 )
 
 maven_jar(
     name = "jetty-jmx",
     artifact = "org.eclipse.jetty:jetty-jmx:" + JETTY_VERS,
-    sha1 = "22be18a055850a6cf3b0efd56c789c3929c87e98",
+    sha1 = "df66265ec011d8b33a7fa541774257deb957ecb4",
 )
 
 maven_jar(
     name = "jetty-http",
     artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VERS,
-    sha1 = "d3f0b0fb016ef8d35ffb199d928ffbcbfa121c86",
+    sha1 = "722ba6ef20eb58c55868f1ce85411e6af13be98e",
 )
 
 maven_jar(
     name = "jetty-io",
     artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VERS,
-    sha1 = "dcb6d4d505ef74898e3a64a38c40195c01e97119",
+    sha1 = "e85e7c4f298efb36b80cc53d635f2da776aa54c2",
 )
 
 maven_jar(
     name = "jetty-util",
     artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VERS,
-    sha1 = "3095acb088f4ff9e3fd9aedf98db73e3c18ea849",
+    sha1 = "44087a126227af5196e3e327a5e11aad1b28852c",
 )
 
 maven_jar(
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
index c3f772a..ab91185 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -23,6 +23,7 @@
 class CloneUsingBothProtocols extends GitSimulation {
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue
   private val default: String = name
+  private val duration: Int = 2
 
   override def replaceOverride(in: String): String = {
     replaceKeyWith("_project", default, in)
@@ -37,14 +38,15 @@
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(2 seconds),
-      constantUsersPerSec(1) during (2 seconds)
+      nothingFor(stepWaitTime(this) seconds),
+      constantUsersPerSec(1) during (duration seconds)
     ),
     deleteProject.test.inject(
-      nothingFor(6 seconds),
+      nothingFor(stepWaitTime(deleteProject) + duration seconds),
       atOnceUsers(1)
     ),
   ).protocols(gitProtocol, httpProtocol)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
index 75fb0b7..39b6d42 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
@@ -26,6 +26,8 @@
   private val default: String = name
   private val numberKey = "_number"
 
+  override def relativeRuntimeWeight = 2
+
   val test: ScenarioBuilder = scenario(unique)
       .feed(data)
       .exec(httpRequest
@@ -42,18 +44,19 @@
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(2 seconds),
+      nothingFor(stepWaitTime(this) seconds),
       atOnceUsers(1)
     ),
     deleteChange.test.inject(
-      nothingFor(6 seconds),
+      nothingFor(stepWaitTime(deleteChange) seconds),
       atOnceUsers(1)
     ),
     deleteProject.test.inject(
-      nothingFor(8 seconds),
+      nothingFor(stepWaitTime(deleteProject) seconds),
       atOnceUsers(1)
     ),
   ).protocols(httpProtocol)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
index 0466ced..f3a2d14 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
@@ -23,6 +23,8 @@
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue
   var number: Option[Int] = None
 
+  override def relativeRuntimeWeight = 2
+
   val test: ScenarioBuilder = scenario(unique)
       .feed(data)
       .exec(session => {
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
index 06f0bdf..36df627 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -31,6 +31,26 @@
   protected val body: String = s"$pathName-body.json"
   protected val unique: String = name + "-" + this.hashCode()
 
+  private val powerFactor: Double = replaceProperty("power_factor", 1.0).toDouble
+  private val SecondsPerWeightUnit: Int = 2
+  val maxExecutionTime: Int = (SecondsPerWeightUnit * relativeRuntimeWeight * powerFactor).toInt
+  private var cumulativeWaitTime: Int = 0
+
+  /**
+   * How long a scenario step should wait before starting to execute.
+   * This is also registering that step's resulting wait time, so that time
+   * can be reused cumulatively by a potentially following scenario step.
+   * (Otherwise, the Gatling set-up scenario steps execute all at once.)
+   *
+   * @param scenario for which to return a wait time.
+   * @return that step's wait time as an Int.
+   */
+  protected def stepWaitTime(scenario: GerritSimulation): Int = {
+    val currentWaitTime = cumulativeWaitTime
+    cumulativeWaitTime += scenario.maxExecutionTime
+    currentWaitTime
+  }
+
   protected val httpRequest: HttpRequestBuilder = http(unique).post("${url}")
   protected val httpProtocol: HttpProtocolBuilder = http.basicAuth(
     conf.httpConfiguration.userName,
@@ -54,11 +74,15 @@
     replaceProperty(term, term, in)
   }
 
+  private def replaceProperty(term: String, default: Any): String = {
+    replaceProperty(term, default, term.toUpperCase)
+  }
+
   protected def replaceProperty(term: String, default: Any, in: String): String = {
     val property = pack + "." + term
     var value = default
     default match {
-      case _: String =>
+      case _: String | _: Double =>
         val propertyValue = Option(System.getProperty(property))
         if (propertyValue.nonEmpty) {
           value = propertyValue.get
@@ -84,8 +108,26 @@
    * override def replaceOverride(in: String): String = {
    * // Simple e.g., replaceProperty("EXTENSION_JSON_KEY", "default", in)
    * </pre>
+   *
+   * @param in which string to perform the replacements.
+   * @return the resulting String.
    */
   def replaceOverride(in: String): String = {
     in
   }
+
+  /**
+   * Meant to be optionally overridden by (heavier) scenarios.
+   * This is the relative runtime weight of the scenario class or type,
+   * compared to other scenarios' own runtime weights.
+   *
+   * The default weight or unit of weight is the pre-assigned value below.
+   * This default applies to any scenario class that is not overriding it
+   * with a greater, relative runtime weight value. Overriding scenarios
+   * happen to relatively require more run time than siblings, prior to
+   * being expected as completed.
+   *
+   * @return the relative runtime weight of this scenario as an Int.
+   */
+  def relativeRuntimeWeight = 1
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
index e0b3206d..74dc052 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -24,6 +24,8 @@
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).circular
   private val default: String = name
 
+  override def relativeRuntimeWeight = 30
+
   override def replaceOverride(in: String): String = {
     replaceKeyWith("_project", default, in)
   }
@@ -36,22 +38,24 @@
 
   private val createProject = new CreateProject(default)
   private val deleteProject = new DeleteProject(default)
+  private val maxBeforeDelete: Int = maxExecutionTime - deleteProject.maxExecutionTime
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(4 seconds),
+      nothingFor(stepWaitTime(this) seconds),
       atOnceUsers(10),
       rampUsers(10) during (5 seconds),
       constantUsersPerSec(20) during (15 seconds),
       constantUsersPerSec(20) during (15 seconds) randomized
     ),
     deleteProject.test.inject(
-      nothingFor(59 seconds),
+      nothingFor(maxBeforeDelete seconds),
       atOnceUsers(1)
     ),
   ).protocols(gitProtocol, httpProtocol)
-      .maxDuration(61 seconds)
+      .maxDuration(maxExecutionTime seconds)
 }
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index ef3f415..1c46ed6 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -107,6 +107,7 @@
         "//lib/auto:auto-value-annotations",
         "//lib/bouncycastle:bcpkix-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
+        "//lib/commons:codec",
         "//lib/commons:compress",
         "//lib/commons:dbcp",
         "//lib/commons:lang",
diff --git a/java/com/google/gerrit/server/config/ChangeUpdateExecutor.java b/java/com/google/gerrit/server/config/ChangeUpdateExecutor.java
deleted file mode 100644
index 4c9e5f0..0000000
--- a/java/com/google/gerrit/server/config/ChangeUpdateExecutor.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2012 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.config;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.gerrit.server.update.BatchUpdate;
-import com.google.inject.BindingAnnotation;
-import java.lang.annotation.Retention;
-
-/**
- * Marker on the global {@link ListeningExecutorService} used by asynchronous {@link BatchUpdate}s.
- */
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface ChangeUpdateExecutor {}
diff --git a/java/com/google/gerrit/server/config/SysExecutorModule.java b/java/com/google/gerrit/server/config/SysExecutorModule.java
index 6c729d9..e7f4540 100644
--- a/java/com/google/gerrit/server/config/SysExecutorModule.java
+++ b/java/com/google/gerrit/server/config/SysExecutorModule.java
@@ -14,19 +14,13 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.server.FanOutExecutor;
 import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.Config;
 
 /**
@@ -73,28 +67,4 @@
     }
     return queues.createQueue(poolSize, "FanOut");
   }
-
-  @Provides
-  @Singleton
-  @ChangeUpdateExecutor
-  public ListeningExecutorService createChangeUpdateExecutor(@GerritServerConfig Config config) {
-    int poolSize = config.getInt("receive", null, "changeUpdateThreads", 1);
-    if (poolSize <= 1) {
-      return MoreExecutors.newDirectExecutorService();
-    }
-    return MoreExecutors.listeningDecorator(
-        new LoggingContextAwareExecutorService(
-            MoreExecutors.getExitingExecutorService(
-                new ThreadPoolExecutor(
-                    1,
-                    poolSize,
-                    10,
-                    TimeUnit.MINUTES,
-                    new ArrayBlockingQueue<>(poolSize),
-                    new ThreadFactoryBuilder()
-                        .setNameFormat("ChangeUpdate-%d")
-                        .setDaemon(true)
-                        .build(),
-                    new ThreadPoolExecutor.CallerRunsPolicy()))));
-  }
 }
diff --git a/java/com/google/gerrit/server/logging/LoggingContext.java b/java/com/google/gerrit/server/logging/LoggingContext.java
index bc5634df..36c7e9e 100644
--- a/java/com/google/gerrit/server/logging/LoggingContext.java
+++ b/java/com/google/gerrit/server/logging/LoggingContext.java
@@ -144,8 +144,7 @@
   }
 
   boolean isLoggingForced() {
-    Boolean force = forceLogging.get();
-    return force != null ? force : false;
+    return Boolean.TRUE.equals(forceLogging.get());
   }
 
   boolean forceLogging(boolean force) {
@@ -155,7 +154,7 @@
     } else {
       forceLogging.remove();
     }
-    return oldValue != null ? oldValue : false;
+    return Boolean.TRUE.equals(oldValue);
   }
 
   boolean isPerformanceLogging() {
diff --git a/java/com/google/gerrit/server/mail/SignedToken.java b/java/com/google/gerrit/server/mail/SignedToken.java
index a010e30..7dcac1a 100644
--- a/java/com/google/gerrit/server/mail/SignedToken.java
+++ b/java/com/google/gerrit/server/mail/SignedToken.java
@@ -22,6 +22,7 @@
 import javax.crypto.Mac;
 import javax.crypto.ShortBufferException;
 import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
 
 /**
  * Utility function to compute and verify XSRF tokens.
@@ -46,7 +47,7 @@
   public static String generateRandomKey() {
     final byte[] r = new byte[26];
     new SecureRandom().nextBytes(r);
-    return encodeBase64(r);
+    return encodeBase64PrivateKey(r);
   }
 
   private final int maxAge;
@@ -63,7 +64,7 @@
    */
   public SignedToken(final int age, final String keyBase64) throws XsrfException {
     maxAge = age > 5 ? age / 5 : age;
-    key = new SecretKeySpec(decodeBase64(keyBase64), MAC_ALG);
+    key = new SecretKeySpec(decodeBase64PrivateKey(keyBase64), MAC_ALG);
     rng = new SecureRandom();
     tokenLength = 2 * INT_SZ + newMac().getMacLength();
   }
@@ -169,6 +170,14 @@
     return (int) (System.currentTimeMillis() / 5000L);
   }
 
+  private static byte[] decodeBase64PrivateKey(final String privateKeyBase64String) {
+    return Base64.decodeBase64(toBytes(privateKeyBase64String));
+  }
+
+  private static String encodeBase64PrivateKey(final byte[] buf) {
+    return toString(Base64.encodeBase64(buf));
+  }
+
   private static byte[] decodeBase64(final String s) {
     return BaseEncoding.base64Url().decode(s);
   }
@@ -208,4 +217,12 @@
     }
     return r;
   }
+
+  private static String toString(final byte[] b) {
+    final StringBuilder r = new StringBuilder(b.length);
+    for (int i = 0; i < b.length; i++) {
+      r.append((char) b[i]);
+    }
+    return r.toString();
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 0ba4905..44dc6e1 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -19,7 +19,6 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
@@ -47,6 +46,7 @@
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.GroupCollector;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -173,7 +173,6 @@
         TimeUtil.nowTs(),
         null,
         null,
-        null,
         null);
   }
 
@@ -205,7 +204,7 @@
       throws IOException, InvalidChangeOperationException, UpdateException, RestApiException,
           ConfigInvalidException, NoSuchProjectException {
     return cherryPick(
-        sourceChange, project, sourceCommit, input, dest, TimeUtil.nowTs(), null, null, null, null);
+        sourceChange, project, sourceCommit, input, dest, TimeUtil.nowTs(), null, null, null);
   }
 
   /**
@@ -227,8 +226,6 @@
    * @param idForNewChange The ID that the new change of the cherry pick will have. If provided and
    *     the cherry-pick doesn't result in creating a new change, then
    *     InvalidChangeOperationException is thrown.
-   * @param groupName The name of the group for grouping related changes (used by GetRelated
-   *     endpoint).
    * @return Result object that describes the cherry pick.
    * @throws IOException Unable to open repository or read from the database.
    * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
@@ -248,8 +245,7 @@
       Timestamp timestamp,
       @Nullable Change.Id revertedChange,
       @Nullable ObjectId changeIdForNewChange,
-      @Nullable Change.Id idForNewChange,
-      @Nullable String groupName)
+      @Nullable Change.Id idForNewChange)
       throws IOException, InvalidChangeOperationException, UpdateException, RestApiException,
           ConfigInvalidException, NoSuchProjectException {
 
@@ -379,12 +375,12 @@
                     cherryPickCommit,
                     dest.branch(),
                     newTopic,
+                    project,
                     sourceChange,
                     sourceCommit,
                     input,
                     revertedChange,
-                    idForNewChange,
-                    groupName);
+                    idForNewChange);
           }
           bu.execute();
           return Result.create(changeId, cherryPickCommit.getFilesWithGitConflicts());
@@ -473,13 +469,13 @@
       CodeReviewCommit cherryPickCommit,
       String refName,
       String topic,
+      Project.NameKey project,
       @Nullable Change sourceChange,
       @Nullable ObjectId sourceCommit,
       CherryPickInput input,
       @Nullable Change.Id revertOf,
-      @Nullable Change.Id idForNewChange,
-      @Nullable String groupName)
-      throws IOException {
+      @Nullable Change.Id idForNewChange)
+      throws IOException, InvalidChangeOperationException {
     Change.Id changeId = idForNewChange != null ? idForNewChange : Change.id(seq.nextChangeId());
     ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
     ins.setRevertOf(revertOf);
@@ -506,8 +502,23 @@
       Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
       ccs.remove(user.get().getAccountId());
       ins.setReviewersAndCcs(reviewers, ccs);
-      if (groupName != null) {
-        ins.setGroups(ImmutableList.of(groupName));
+    }
+    // If there is a base, and the base is not merged, the groups will be overridden by the base's
+    // groups.
+    ins.setGroups(GroupCollector.getDefaultGroups(cherryPickCommit.getId()));
+    if (input.base != null) {
+      List<ChangeData> changes =
+          queryProvider.get().setLimit(2).byBranchCommitOpen(project.get(), refName, input.base);
+      if (changes.size() > 1) {
+        throw new InvalidChangeOperationException(
+            "Several changes with key "
+                + input.base
+                + " reside on the same branch. "
+                + "Cannot cherry-pick on target branch.");
+      }
+      if (changes.size() == 1) {
+        Change change = changes.get(0).change();
+        ins.setGroups(changeNotesFactory.createChecked(change).getCurrentPatchSet().groups());
       }
     }
     bu.insertChange(ins);
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 7a04f05..846b80c 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -286,7 +286,6 @@
       throws IOException, RestApiException, UpdateException, ConfigInvalidException,
           PermissionBackendException {
 
-    String groupName = null;
     String initialMessage = revertInput.message;
     while (sortedChangesInProjectAndBranch.hasNext()) {
       ChangeNotes changeNotes = sortedChangesInProjectAndBranch.next().data().notes();
@@ -300,13 +299,8 @@
         // This is the code in case this is the first revert of this project + branch, and the
         // revert would be on top of the change being reverted.
         craeteNormalRevert(revertInput, changeNotes, timestamp);
-        groupName = cherryPickInput.base;
       } else {
-        // This is the code in case this is the second revert (or more) of this project + branch.
-        if (groupName == null) {
-          groupName = cherryPickInput.base;
-        }
-        createCherryPickedRevert(revertInput, project, groupName, changeNotes, timestamp);
+        createCherryPickedRevert(revertInput, project, changeNotes, timestamp);
       }
     }
   }
@@ -314,7 +308,6 @@
   private void createCherryPickedRevert(
       RevertInput revertInput,
       Project.NameKey project,
-      String groupName,
       ChangeNotes changeNotes,
       Timestamp timestamp)
       throws IOException, ConfigInvalidException, UpdateException, RestApiException {
@@ -333,7 +326,7 @@
       bu.addOp(
           changeNotes.getChange().getId(),
           new CreateCherryPickOp(
-              revCommitId, generatedChangeId, cherryPickRevertChangeId, groupName, timestamp));
+              revCommitId, generatedChangeId, cherryPickRevertChangeId, timestamp));
       bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
       bu.addOp(
           cherryPickRevertChangeId,
@@ -547,19 +540,16 @@
     private final ObjectId revCommitId;
     private final ObjectId computedChangeId;
     private final Change.Id cherryPickRevertChangeId;
-    private final String groupName;
     private final Timestamp timestamp;
 
     CreateCherryPickOp(
         ObjectId revCommitId,
         ObjectId computedChangeId,
         Change.Id cherryPickRevertChangeId,
-        String groupName,
         Timestamp timestamp) {
       this.revCommitId = revCommitId;
       this.computedChangeId = computedChangeId;
       this.cherryPickRevertChangeId = cherryPickRevertChangeId;
-      this.groupName = groupName;
       this.timestamp = timestamp;
     }
 
@@ -577,8 +567,7 @@
               timestamp,
               change.getId(),
               computedChangeId,
-              cherryPickRevertChangeId,
-              groupName);
+              cherryPickRevertChangeId);
       // save the commit as base for next cherryPick of that branch
       cherryPickInput.base =
           changeNotesFactory
diff --git a/java/com/google/gerrit/testing/ConfigSuite.java b/java/com/google/gerrit/testing/ConfigSuite.java
index c3a4192..d7ae397 100644
--- a/java/com/google/gerrit/testing/ConfigSuite.java
+++ b/java/com/google/gerrit/testing/ConfigSuite.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.logging.LoggingContext;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -107,16 +106,6 @@
  * field annotated with {@code @ConfigSuite.Name}.
  */
 public class ConfigSuite extends Suite {
-  private static final String FLOGGER_BACKEND_PROPERTY = "flogger.backend_factory";
-  private static final String FLOGGER_LOGGING_CONTEXT = "flogger.logging_context";
-
-  static {
-    System.setProperty(
-        FLOGGER_BACKEND_PROPERTY,
-        "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
-    System.setProperty(FLOGGER_LOGGING_CONTEXT, LoggingContext.class.getName() + "#getInstance");
-  }
-
   public static final String DEFAULT = "default";
 
   @Target({METHOD})
diff --git a/java/com/google/gerrit/testing/FloggerInitializer.java b/java/com/google/gerrit/testing/FloggerInitializer.java
new file mode 100644
index 0000000..1972107
--- /dev/null
+++ b/java/com/google/gerrit/testing/FloggerInitializer.java
@@ -0,0 +1,31 @@
+// 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.testing;
+
+import com.google.gerrit.server.logging.LoggingContext;
+
+public class FloggerInitializer {
+  private static final String FLOGGER_BACKEND_PROPERTY = "flogger.backend_factory";
+  private static final String FLOGGER_LOGGING_CONTEXT = "flogger.logging_context";
+
+  private FloggerInitializer() {}
+
+  public static void initBackend() {
+    System.setProperty(
+        FLOGGER_BACKEND_PROPERTY,
+        "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
+    System.setProperty(FLOGGER_LOGGING_CONTEXT, LoggingContext.class.getName() + "#getInstance");
+  }
+}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index af9d8c3..baccbf9 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -18,7 +18,6 @@
 import static com.google.inject.Scopes.SINGLETON;
 
 import com.google.common.base.Strings;
-import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperationsImpl;
@@ -48,7 +47,6 @@
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
-import com.google.gerrit.server.config.ChangeUpdateExecutor;
 import com.google.gerrit.server.config.DefaultUrlFormatter;
 import com.google.gerrit.server.config.GerritGlobalModule;
 import com.google.gerrit.server.config.GerritInstanceNameModule;
@@ -186,9 +184,6 @@
     bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
     bind(InMemoryRepositoryManager.class).in(SINGLETON);
     bind(TrackingFooters.class).toProvider(TrackingFootersProvider.class).in(SINGLETON);
-    bind(ListeningExecutorService.class)
-        .annotatedWith(ChangeUpdateExecutor.class)
-        .toInstance(MoreExecutors.newDirectExecutorService());
     bind(SecureStore.class).to(DefaultSecureStore.class);
 
     install(new InMemorySchemaModule());
diff --git a/java/com/google/gerrit/testing/TestLoggingActivator.java b/java/com/google/gerrit/testing/TestLoggingActivator.java
index 2049bfd..a766429 100644
--- a/java/com/google/gerrit/testing/TestLoggingActivator.java
+++ b/java/com/google/gerrit/testing/TestLoggingActivator.java
@@ -83,6 +83,7 @@
 
   public static void configureLogging() {
     LogManager.resetConfiguration();
+    FloggerInitializer.initBackend();
 
     PatternLayout layout = new PatternLayout();
     layout.setConversionPattern("%-5p %c %x: %m%n");
@@ -99,4 +100,6 @@
 
     LOG_LEVELS.entrySet().stream().forEach(e -> getLogger(e.getKey()).setLevel(e.getValue()));
   }
+
+  private TestLoggingActivator() {}
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 17391de..74f9134 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1097,6 +1097,57 @@
   }
 
   @Test
+  public void getRelatedCherryPicks() throws Exception {
+    PushOneCommit.Result r1 = createChange(SUBJECT, "a.txt", "a");
+    PushOneCommit.Result r2 = createChange(SUBJECT, "b.txt", "b");
+
+    String branch = "foo";
+    // Create target branch to cherry-pick to.
+    gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+
+    CherryPickInput input = new CherryPickInput();
+    input.message = "message";
+    input.destination = branch;
+    ChangeInfo firstCherryPickResult =
+        gApi.changes().id(r1.getChangeId()).current().cherryPickAsInfo(input);
+
+    input.base = gApi.changes().id(firstCherryPickResult.changeId).current().commit(false).commit;
+    ChangeInfo secondCherryPickResult =
+        gApi.changes().id(r2.getChangeId()).current().cherryPickAsInfo(input);
+    assertThat(gApi.changes().id(firstCherryPickResult.changeId).current().related().changes)
+        .hasSize(2);
+    assertThat(gApi.changes().id(secondCherryPickResult.changeId).current().related().changes)
+        .hasSize(2);
+  }
+
+  @Test
+  public void cherryPickOnMergedChangeIsNotRelated() throws Exception {
+    PushOneCommit.Result r1 = createChange(SUBJECT, "a.txt", "a");
+    PushOneCommit.Result r2 = createChange(SUBJECT, "b.txt", "b");
+
+    String branch = "foo";
+    // Create target branch to cherry-pick to.
+    gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+
+    CherryPickInput input = new CherryPickInput();
+    input.message = "message";
+    input.destination = branch;
+    ChangeInfo firstCherryPickResult =
+        gApi.changes().id(r1.getChangeId()).current().cherryPickAsInfo(input);
+
+    gApi.changes().id(firstCherryPickResult.id).current().review(ReviewInput.approve());
+    gApi.changes().id(firstCherryPickResult.id).current().submit();
+
+    input.base = gApi.changes().id(firstCherryPickResult.changeId).current().commit(false).commit;
+    ChangeInfo secondCherryPickResult =
+        gApi.changes().id(r2.getChangeId()).current().cherryPickAsInfo(input);
+    assertThat(gApi.changes().id(firstCherryPickResult.changeId).current().related().changes)
+        .hasSize(0);
+    assertThat(gApi.changes().id(secondCherryPickResult.changeId).current().related().changes)
+        .hasSize(0);
+  }
+
+  @Test
   public void canRebase() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r1 = push.to("refs/for/master");
diff --git a/javatests/com/google/gerrit/server/mail/SignedTokenTest.java b/javatests/com/google/gerrit/server/mail/SignedTokenTest.java
index cfb551f..41d8d69 100644
--- a/javatests/com/google/gerrit/server/mail/SignedTokenTest.java
+++ b/javatests/com/google/gerrit/server/mail/SignedTokenTest.java
@@ -18,20 +18,14 @@
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
 import java.util.Random;
+import java.util.regex.Pattern;
 import org.junit.Before;
 import org.junit.Test;
 
 public class SignedTokenTest {
 
-  private static final String REGISTER_EMAIL_PRIVATE_KEY =
-      "R2Vycml0JTIwcmVnaXN0ZXJFbWFpbFByaXZhdGVLZXk=";
-  private static final String URL_SAFE_REGISTER_EMAIL_PRIVATE_KEY =
-      REGISTER_EMAIL_PRIVATE_KEY.replaceFirst("R2", "_-");
-  private static final String URL_UNSAFE_REGISTER_EMAIL_PRIVATE_KEY_WITH_PLUS =
-      REGISTER_EMAIL_PRIVATE_KEY.replaceFirst("R", "+");
-  private static final String URL_UNSAFE_REGISTER_EMAIL_PRIVATE_KEY_WITH_SLASH =
-      REGISTER_EMAIL_PRIVATE_KEY.replaceFirst("R", "/");
-
+  private static final Pattern URL_UNSAFE_CHARS = Pattern.compile("(\\+|/)");
+  private static final String REGISTER_EMAIL_PRIVATE_KEY = "TGMv3/bTC42jUKQndTQrXyHhHYMP0t69i/4=";
   private static final int maxAge = 5;
   private static final String TEXT = "This is a text";
   private static final String FORGED_TEXT = "This is a forged text";
@@ -44,29 +38,23 @@
     signedToken = new SignedToken(maxAge, REGISTER_EMAIL_PRIVATE_KEY);
   }
 
-  /**
-   * Test new token: the key is a normal BASE64 string without index of '62'(+ or _) or '63'(/ or -)
-   */
+  /** Test new token: the key is a normal BASE64 string that can be used for URL safely */
   @Test
   public void newTokenKeyDoesNotContainUnsafeChar() throws Exception {
-    new SignedToken(maxAge, REGISTER_EMAIL_PRIVATE_KEY);
-  }
-
-  /** Test new token: the key is an URL safe BASE64 string with indexes of '62'(_) and '63'(-) */
-  @Test
-  public void newTokenWithUrlSafeBase64() throws Exception {
-    new SignedToken(maxAge, URL_SAFE_REGISTER_EMAIL_PRIVATE_KEY);
+    assertThat(signedToken.newToken(TEXT)).doesNotContainMatch(URL_UNSAFE_CHARS);
   }
 
   /** Test new token: the key is an URL unsafe BASE64 string with index of '62'(+) */
   @Test
   public void newTokenWithUrlUnsafeBase64Plus() throws Exception {
-    IllegalArgumentException thrown =
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> new SignedToken(maxAge, URL_UNSAFE_REGISTER_EMAIL_PRIVATE_KEY_WITH_PLUS));
+    String token = "+" + signedToken.newToken(TEXT);
+    CheckTokenException thrown =
+        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(token, TEXT));
+
+    assertThat(thrown).hasMessageThat().contains("decoding failed");
 
     assertThat(thrown)
+        .hasCauseThat()
         .hasMessageThat()
         .isEqualTo(
             "com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: +");
@@ -75,12 +63,14 @@
   /** Test new token: the key is an URL unsafe BASE64 string with '63'(/) */
   @Test
   public void newTokenWithUrlUnsafeBase64Slash() throws Exception {
-    IllegalArgumentException thrown =
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> new SignedToken(maxAge, URL_UNSAFE_REGISTER_EMAIL_PRIVATE_KEY_WITH_SLASH));
+    String token = "/" + signedToken.newToken(TEXT);
+    CheckTokenException thrown =
+        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(token, TEXT));
+
+    assertThat(thrown).hasMessageThat().contains("decoding failed");
 
     assertThat(thrown)
+        .hasCauseThat()
         .hasMessageThat()
         .isEqualTo(
             "com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: /");
diff --git a/modules/jgit b/modules/jgit
index 9b2f8ce..e624e13 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 9b2f8ced9018dad519fefdc19d3940104b95434a
+Subproject commit e624e1310836747c15afeb411d9fd7eaee41ddae
diff --git a/plugins/delete-project b/plugins/delete-project
index c3dde41..34ea95f 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit c3dde41c212b69e076b3535f913ecd22f02869b2
+Subproject commit 34ea95f9c7997aaa0b2275fadbbd00acc2fcbde8
diff --git a/plugins/gitiles b/plugins/gitiles
index df5d2e5..641476e 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit df5d2e55b1126c7bc7c589a9971f758b0dae5ce7
+Subproject commit 641476e153143c2b67e334b35626beb9b2534956
diff --git a/plugins/plugin-manager b/plugins/plugin-manager
index 2933add..93d8e12 160000
--- a/plugins/plugin-manager
+++ b/plugins/plugin-manager
@@ -1 +1 @@
-Subproject commit 2933add62ecf2cbfc28cfe2cff81ff0e0eecc913
+Subproject commit 93d8e1248478c6b2db0a8c1080f2766406dd213d
diff --git a/plugins/replication b/plugins/replication
index 864bc26..ab6af46 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 864bc26714111ed8fd4e7b402e7830482b57cde0
+Subproject commit ab6af46f5d191c10a163f4d8a77b75e920bde372
diff --git a/plugins/webhooks b/plugins/webhooks
index 11510b8..b539049 160000
--- a/plugins/webhooks
+++ b/plugins/webhooks
@@ -1 +1 @@
-Subproject commit 11510b804a5491a2a4368a5b0681d11cb1e77240
+Subproject commit b539049a9e2073b7e9af8b151d8ea9c1087e12da
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 101f304..906ed34 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -241,11 +241,6 @@
     return mappedLines.join('\n').trim();
   }
 
-  _isMessageContentEmpty() {
-    return !this._messageContentExpanded
-        || this._messageContentExpanded.length === 0;
-  }
-
   _computeAuthor(message) {
     return message.author || message.updated_by;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
index c8e1cc1..753fd38 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
@@ -217,7 +217,7 @@
             content="[[_messageContentExpanded]]"
             config="[[_projectConfig.commentlinks]]"
           ></gr-formatted-text>
-          <template is="dom-if" if="[[!_isMessageContentEmpty()]]">
+          <template is="dom-if" if="[[_messageContentExpanded]]">
             <div
               class="replyActionContainer"
               hidden$="[[!showReplyButton]]"
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index 5e2feb4..4651e66 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -425,6 +425,32 @@
           element.shadowRoot.querySelector('.deleteBtn').hidden
       );
     });
+
+    test('reply button shown when message is updated', () => {
+      element.message = undefined;
+      flushAsynchronousOperations();
+      let replyEl = element.shadowRoot.querySelector('.replyActionContainer');
+      // We don't even expect the button to show up in the DOM when the message
+      // is undefined.
+      assert.isNotOk(replyEl);
+
+      element.message = {
+        id: '47c43261_55aa2c41',
+        author: {
+          _account_id: 1115495,
+          name: 'Andrew Bonventre',
+          email: 'andybons@chromium.org',
+        },
+        date: '2016-01-12 20:24:49.448000000',
+        message: 'not empty',
+        _revision_number: 1,
+        expanded: false,
+      };
+      flushAsynchronousOperations();
+      replyEl = element.shadowRoot.querySelector('.replyActionContainer');
+      assert.isOk(replyEl);
+      assert.isFalse(replyEl.hidden);
+    });
   });
 });
 </script>
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 26486c7..4240a9b 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>3.2.0-rc0</version>
+  <version>3.2.0-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 54a962d..cf2b080 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>3.2.0-rc0</version>
+  <version>3.2.0-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 c45ba54..7d3c4f0 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>3.2.0-rc0</version>
+  <version>3.2.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index 98a643e..9478283 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>3.2.0-rc0</version>
+  <version>3.2.0-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index 7fadac6..fb1e5ca 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 = "3.2.0-rc0"
+GERRIT_VERSION = "3.2.0-SNAPSHOT"