Merge "Merge branch 'stable-3.0' into stable-3.1" into stable-3.1
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 20484e6..bf35c32 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -228,6 +228,17 @@
Scenario development is often done using locally running Gerrit systems under test, which are
sometimes dockerized.
+==== Number of users
+
+The `number_of_users` property can be used to scale scenario steps to run with the specified number
+of concurrent users. The value of this property remains `1` by default. For example, this sets the
+number of concurrent users to 10:
+
+* `-Dcom.google.gerrit.scenarios.number_of_users=10`
+
+This will make scenarios that support the `number_of_users` property to inject that many users
+concurrently for load testing.
+
== How to run tests
Run all tests:
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json
new file mode 100644
index 0000000..f69e575
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json
@@ -0,0 +1,3 @@
+{
+ "revision": "master"
+}
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json
new file mode 100644
index 0000000..5459f11
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/PROJECT/branches/",
+ "project": "PROJECT"
+ }
+]
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 08966a8..c283861 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
@@ -21,9 +21,9 @@
import scala.concurrent.duration._
class CloneUsingBothProtocols extends GitSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
private val projectName = className
- private val duration = 2
+ private val duration = 2 * numberOfUsers
override def replaceOverride(in: String): String = {
replaceKeyWith("_project", projectName, in)
@@ -43,7 +43,7 @@
),
test.inject(
nothingFor(stepWaitTime(this) seconds),
- constantUsersPerSec(single) during (duration seconds)
+ constantUsersPerSec(numberOfUsers) during (duration seconds)
).protocols(gitProtocol),
deleteProject.test.inject(
nothingFor(stepWaitTime(deleteProject) + duration seconds),
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala
new file mode 100644
index 0000000..ccde633
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala
@@ -0,0 +1,56 @@
+// 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.scenarios
+
+import io.gatling.core.Predef.{atOnceUsers, _}
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef._
+
+import scala.concurrent.duration._
+
+class CreateBranch extends ProjectSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private val branchIdKey = "branchId"
+ private var counter = 0
+
+ private val test: ScenarioBuilder = scenario(uniqueName)
+ .feed(data)
+ .exec(session => {
+ counter += 1
+ session.set(branchIdKey, "branch-" + counter)
+ })
+ .exec(http(uniqueName)
+ .post("${url}${" + branchIdKey + "}")
+ .body(ElFileBody(body)).asJson)
+
+ private val createProject = new CreateProject(projectName)
+ private val deleteProject = new DeleteProject(projectName)
+
+ setUp(
+ createProject.test.inject(
+ nothingFor(stepWaitTime(createProject) seconds),
+ atOnceUsers(single)
+ ),
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteProject.test.inject(
+ nothingFor(stepWaitTime(deleteProject) seconds),
+ atOnceUsers(single)
+ ),
+ ).protocols(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 f3692a9..b0063cb 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
@@ -19,12 +19,14 @@
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._
+import scala.collection.mutable
import scala.concurrent.duration._
class CreateChange extends ProjectSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
private val numberKey = "_number"
var number = 0
+ var numbers: mutable.Queue[Int] = mutable.Queue[Int]()
override def relativeRuntimeWeight = 2
@@ -40,6 +42,7 @@
.check(regex("\"" + numberKey + "\":(\\d+),").saveAs(numberKey)))
.exec(session => {
number = session(numberKey).as[Int]
+ numbers += number
session
})
@@ -54,11 +57,11 @@
),
test.inject(
nothingFor(stepWaitTime(this) seconds),
- atOnceUsers(single)
+ atOnceUsers(numberOfUsers)
),
deleteChange.test.inject(
nothingFor(stepWaitTime(deleteChange) seconds),
- atOnceUsers(single)
+ atOnceUsers(numberOfUsers)
),
deleteProject.test.inject(
nothingFor(stepWaitTime(deleteProject) seconds),
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 d832bde..e47108f 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
@@ -20,7 +20,7 @@
import io.gatling.http.Predef.http
class DeleteChange extends GerritSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
private var createChange: Option[CreateChange] = None
override def relativeRuntimeWeight = 2
@@ -34,7 +34,7 @@
.feed(data)
.exec(session => {
if (createChange.nonEmpty) {
- session.set("number", createChange.get.number)
+ session.set("number", createChange.get.numbers.dequeue())
} else {
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 7b31b3d..b11c87c 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
@@ -34,6 +34,7 @@
protected val uniqueName: String = className + "-" + hashCode()
protected val single = 1
+ val numberOfUsers: Int = replaceProperty("number_of_users", single).toInt
val replicationDelay: Int = replaceProperty("replication_delay", 15).toInt
private val powerFactor = replaceProperty("power_factor", 1.0).toDouble
protected val SecondsPerWeightUnit = 2
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index be37dd7..4066a10 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -584,6 +584,10 @@
return testInjector;
}
+ public Injector getHttpdInjector() {
+ return daemon.getHttpdInjector();
+ }
+
Description getDescription() {
return desc;
}
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index eb6b2e0..520ce4e 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -357,6 +357,7 @@
private final Metrics metrics;
private final PluginSetContext<RequestListener> requestListeners;
private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
+ private final Provider<WebSession> sessionProvider;
@Inject
UploadFilter(
@@ -366,7 +367,8 @@
GroupAuditService groupAuditService,
Metrics metrics,
PluginSetContext<RequestListener> requestListeners,
- UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook) {
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
+ Provider<WebSession> sessionProvider) {
this.uploadValidatorsFactory = uploadValidatorsFactory;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
@@ -374,6 +376,7 @@
this.metrics = metrics;
this.requestListeners = requestListeners;
this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
+ this.sessionProvider = sessionProvider;
}
@Override
@@ -389,7 +392,7 @@
HttpServletResponseWithStatusWrapper responseWrapper =
new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
HttpServletRequest httpRequest = (HttpServletRequest) request;
- String sessionId = httpRequest.getSession().getId();
+ String sessionId = getSessionIdOrNull(sessionProvider);
try (TraceContext traceContext = TraceContext.open()) {
RequestInfo requestInfo =
@@ -492,6 +495,7 @@
private final Provider<CurrentUser> userProvider;
private final GroupAuditService groupAuditService;
private final Metrics metrics;
+ private final Provider<WebSession> sessionProvider;
@Inject
ReceiveFilter(
@@ -499,12 +503,14 @@
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider,
GroupAuditService groupAuditService,
- Metrics metrics) {
+ Metrics metrics,
+ Provider<WebSession> sessionProvider) {
this.cache = cache;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
this.groupAuditService = groupAuditService;
this.metrics = metrics;
+ this.sessionProvider = sessionProvider;
}
@Override
@@ -544,7 +550,7 @@
} finally {
groupAuditService.dispatch(
new HttpAuditEvent(
- httpRequest.getSession().getId(),
+ getSessionIdOrNull(sessionProvider),
userProvider.get(),
extractWhat(httpRequest),
TimeUtil.nowMs(),
@@ -600,4 +606,12 @@
@Override
public void destroy() {}
}
+
+ private static String getSessionIdOrNull(Provider<WebSession> sessionProvider) {
+ WebSession session = sessionProvider.get();
+ if (session.isSignedIn()) {
+ return session.getSessionId();
+ }
+ return null;
+ }
}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 238cf29..c1a1d7f 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -224,6 +224,11 @@
this.replica = replica;
}
+ @VisibleForTesting
+ public Injector getHttpdInjector() {
+ return httpdInjector;
+ }
+
@Override
public int run() throws Exception {
if (stopOnly) {
diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 0bbb51d..3980bd8 100644
--- a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -17,6 +17,7 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.events.LifecycleListener;
@@ -40,8 +41,11 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.jmx.MBeanContainer;
@@ -198,6 +202,8 @@
private final Metrics metrics;
private boolean reverseProxy;
private ConnectionStatistics connStats;
+ private final SessionHandler sessionHandler;
+ private final AtomicLong sessionsCounter;
@Inject
JettyServer(
@@ -216,8 +222,27 @@
connector.addBean(connStats);
}
metrics = new Metrics(pool, connStats);
+ sessionHandler = new SessionHandler();
+ sessionsCounter = new AtomicLong();
- Handler app = makeContext(env, cfg);
+ /* Code used for testing purposes for making assertions
+ * on the number of active HTTP sessions.
+ */
+ sessionHandler.addEventListener(
+ new HttpSessionListener() {
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se) {
+ sessionsCounter.decrementAndGet();
+ }
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se) {
+ sessionsCounter.incrementAndGet();
+ }
+ });
+
+ Handler app = makeContext(env, cfg, sessionHandler);
if (cfg.getBoolean("httpd", "requestLog", !reverseProxy)) {
RequestLogHandler handler = new RequestLogHandler();
handler.setRequestLog(httpLogFactory.get());
@@ -244,6 +269,11 @@
httpd.setStopAtShutdown(false);
}
+ @VisibleForTesting
+ public long numActiveSessions() {
+ return sessionsCounter.longValue();
+ }
+
Metrics getMetrics() {
return metrics;
}
@@ -443,7 +473,7 @@
return pool;
}
- private Handler makeContext(JettyEnv env, Config cfg) {
+ private Handler makeContext(JettyEnv env, Config cfg, SessionHandler sessionHandler) {
final Set<String> paths = new HashSet<>();
for (URI u : listenURLs(cfg)) {
String p = u.getPath();
@@ -458,7 +488,7 @@
final List<ContextHandler> all = new ArrayList<>();
for (String path : paths) {
- all.add(makeContext(path, env, cfg));
+ all.add(makeContext(path, env, cfg, sessionHandler));
}
if (all.size() == 1) {
@@ -476,13 +506,14 @@
return r;
}
- private ContextHandler makeContext(final String contextPath, JettyEnv env, Config cfg) {
+ private ContextHandler makeContext(
+ final String contextPath, JettyEnv env, Config cfg, SessionHandler sessionHandler) {
final ServletContextHandler app = new ServletContextHandler();
// This enables the use of sessions in Jetty, feature available
// for Gerrit plug-ins to enable user-level sessions.
//
- app.setSessionHandler(new SessionHandler());
+ app.setSessionHandler(sessionHandler);
app.setErrorHandler(new HiddenErrorHandler());
// This is the path we are accessed by clients within our domain.
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java b/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
index fe1c264..35f8270 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
@@ -19,11 +19,15 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.FakeGroupAuditService;
import com.google.gerrit.acceptance.Sandboxed;
-import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.pgm.http.jetty.JettyServer;
import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.inject.Inject;
+import java.util.Optional;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
@@ -33,9 +37,11 @@
public class AbstractGitOverHttpServlet extends AbstractPushForReview {
@Inject protected FakeGroupAuditService auditService;
+ private JettyServer jettyServer;
@Before
public void beforeEach() throws Exception {
+ jettyServer = server.getHttpdInjector().getInstance(JettyServer.class);
CredentialsProvider.setDefault(
new UsernamePasswordCredentialsProvider(admin.username(), admin.httpPassword()));
selectProtocol(AbstractPushForReview.Protocol.HTTP);
@@ -67,33 +73,54 @@
assertThat(receivePack.what).endsWith("/git-receive-pack");
assertThat(receivePack.params).isEmpty();
assertThat(receivePack.httpStatus).isEqualTo(HttpServletResponse.SC_OK);
+ assertThat(jettyServer.numActiveSessions()).isEqualTo(0);
}
@Test
- @Sandboxed
- public void uploadPackAuditEventLog() throws Exception {
+ public void anonymousUploadPackAuditEventLog() throws Exception {
+ uploadPackAuditEventLog(Constants.DEFAULT_REMOTE_NAME, Optional.empty());
+ }
+
+ @Test
+ public void authenticatedUploadPackAuditEventLog() throws Exception {
+ String remote = "authenticated";
+ Config cfg = testRepo.git().getRepository().getConfig();
+
+ String uri = admin.getHttpUrl(server) + "/a/" + project.get();
+ cfg.setString("remote", remote, "url", uri);
+ cfg.setString("remote", remote, "fetch", "+refs/heads/*:refs/remotes/origin/*");
+
+ uploadPackAuditEventLog(remote, Optional.of(admin.id()));
+ }
+
+ private void uploadPackAuditEventLog(String remote, Optional<Account.Id> accountId)
+ throws Exception {
auditService.drainHttpAuditEvents();
// testRepo is already a clone. Make a server-side change so we have something to fetch.
try (Repository repo = repoManager.openRepository(project);
TestRepository<?> testRepo = new TestRepository<>(repo)) {
testRepo.branch("master").commit().create();
}
- testRepo.git().fetch().call();
+ testRepo.git().fetch().setRemote(remote).call();
ImmutableList<HttpAuditEvent> auditEvents = auditService.drainHttpAuditEvents();
assertThat(auditEvents).hasSize(2);
HttpAuditEvent lsRemote = auditEvents.get(0);
- // Repo URL doesn't include /a, so fetching doesn't cause authentication.
- assertThat(lsRemote.who).isInstanceOf(AnonymousUser.class);
+ assertThat(lsRemote.who.toString())
+ .isEqualTo(
+ accountId.map(id -> "IdentifiedUser[account " + id.get() + "]").orElse("ANONYMOUS"));
assertThat(lsRemote.what).endsWith("/info/refs?service=git-upload-pack");
assertThat(lsRemote.params).containsExactly("service", "git-upload-pack");
assertThat(lsRemote.httpStatus).isEqualTo(HttpServletResponse.SC_OK);
HttpAuditEvent uploadPack = auditEvents.get(1);
- assertThat(lsRemote.who).isInstanceOf(AnonymousUser.class);
+ assertThat(uploadPack.who.toString())
+ .isEqualTo(
+ accountId.map(id -> "IdentifiedUser[account " + id.get() + "]").orElse("ANONYMOUS"));
assertThat(uploadPack.what).endsWith("/git-upload-pack");
assertThat(uploadPack.params).isEmpty();
assertThat(uploadPack.httpStatus).isEqualTo(HttpServletResponse.SC_OK);
+ assertThat(jettyServer.numActiveSessions()).isEqualTo(0);
}
}
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 5554577..71b6480 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.1.12-SNAPSHOT</version>
+ <version>3.1.13-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 cad1918..5a8a948 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.1.12-SNAPSHOT</version>
+ <version>3.1.13-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 f4cc3b3..6385481 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.1.12-SNAPSHOT</version>
+ <version>3.1.13-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 69da6b7..ffb1dae 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.1.12-SNAPSHOT</version>
+ <version>3.1.13-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index ff91f0f..95ebd2c 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.1.12-SNAPSHOT"
+GERRIT_VERSION = "3.1.13-SNAPSHOT"